I am having a bit of a trouble scaling my graph, according to the length on the bars. For example, in the jsfiddle, I can't draw a bar beyond the data point of size 25. I know one way to fix this would be to make the width and height of the body larger. But I was thinking scaling the entire graph would be much more efficient, so that one bar doesn't end up looking abnormally large.
http://jsfiddle.net/NkkDC/
I was thinking, I would have to scale the "y" function here, but I wasn't sure how.
bars.on("click", clickEvent)
.transition().duration(2000).delay(200)
.attr("y", function(d, i) { return i * 20; })
.attr("width", x)
.attr("height", 20);
Thanks in advance :)
The input domain of your xScale can change every time you add a new value (since you could add a new maximum), so we need to recalculate the xScale when we re-render the chart. Moving the declaration of the x-scale inside your render function should do the trick :
var xScale = d3.scale.linear()
.domain([0, d3.max(data)])
.range([0, 420]);
http://jsfiddle.net/NkkDC/1/
Related
I'm trying to update my x axis in a D3js bar chart (is partially working) depending on a user filter, the bars are actually changing but is not doing it well. I don't really know where is the problem and I need some help.
in this part of the code I'm updating the bar chart
function update(selectedGroup) {
svg.selectAll("rect").remove()
var groups = d3.map(dataFilter, function(d){return(d.group)}).keys();
x.domain(groups);
var dataFilter = result.filter(function(d){return d.group==selectedGroup});
console.log(dataFilter);
var rectG=rectangulos(dataFilter)
}
the complete bar chart
how is working now:
the result should be something like this
I have an live example here
There is a relatively straightforward reason you are seeing this behavior.
When the domain of the scale x is all the groups x.bandwidth() is small. But when the domain of x is only one value, x.bandwidth() is large. In both cases, the first band starts in the same location.
Next we have a nested scale here xSubgroup - the range of this is equal to x.bandwidth(). When the domain of x changes, we need to update the range of xSubgroup. If we don't do this, the bars will still be very thin and start at the beginning of the axis (as the bars' bandwidth aren't changing even if the group's bandwidth does). You don't update the sub scale's range, but we need to do that:
x.domain(groups);
xSubgroup.range([0, x.bandwidth()])
With this we get the update we're looking for.
But the axis labels remain unchanged. Updating a scale doesn't update the axis unless we explicitly do so. I'll break up your method chaining and store a reference for the g holding the axis:
var xAxis = svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
xAxis.selectAll("text")
...
Now we can update the axis, I'm carrying forward the text styling as well. You can simplify the code by using an update function to do all the entering/exiting/updating of axes and data - here we have some duplication in that both the initial set up and the update function have overlap.
To update the axis we use:
// Call the axis again to update
xAxis.call(d3.axisBottom(x))
xAxis.selectAll("text")
.style("text-anchor", "end")
.attr("font-size", "55px")
.attr("y", "-7")
.attr("x", "-7")
.attr("transform", "rotate(-90)");
Which gives the desired behavior if I understand correctly, updated plunkr
I'm working on a heatmap which basically plots the graph between taxIDs and KeywordNames from an external JSON. While I'm able to plot the values I see many blank spaces on the graph and clueless how I can plot them with the available data.
Here's the link to codesandbox: https://codesandbox.io/s/40mnzk9xv4
On the X-Axis I'm plotting the TaxIDs which are being calculated within the given range. I did try using the function rangeBands() but I get an error everytime.
Its the similar case with Y-Axis where I'am plotting the keywordIDs which are also being calculated within a range. I'm trying to print all the KeywordNames on Y axis and all taxIDs on the X-Axis and plot their corresponding spectracount on graph.
Please help me where have I gone wrong.
The output I'm looking for is something similar to this: https://bl.ocks.org/Bl3f/cdb5ad854b376765fa99
Thank you.
Some things to help you get you one your way:
First, your scales should use scaleBand(), not scaleLinear(), as they have discrete domains (i.e. categories of something, rather than continuous)
Second, your scale domains is taking every value of taxId and keywordName in your data as a possible value. But some values are repeated more than once. You need to be filtering them so you only have unique values. So your scale code should be:
const xValues = d3.set(data.map(d => d.taxId)).values();
const yValues = d3.set(data.map(d => d.keywordName)).values();
const xScale = d3.scaleBand()
.range([0, width])
.domain(xValues); //X-Axis
const yScale = d3.scaleBand()
.range([0, height])
.domain(yValues); //Y-Axis
Finally, your code that places the heatmap tiles needs to be calling the scale functions so it works out the position of each rect correctly:
chart.selectAll('g')
.data(data).enter().append('g')
.append('rect')
.attr('x', d => { return xScale(d.taxId) })
.attr('y', d => { return yScale(d.keywordName) })
That should get you most of the way there. You'll want to also reduce cellSize and remove the tickFormat calls on the axes as they are trying to convert your text labels to numbers.
I'm working on a chart for which each observation has a value between [-100,100], and I want to plot each point's position on a scale. The challenge is that the vast majority of the points have values in one region of the scale (the distribution is essentially Gaussian with mean 0).
In the past, when I've needed to plot something like a Zipf probability density distribution, I've used log scales to spread out the points in the congested region. Now my situation is similar, except that I have two distributions for which I need to spread out the points (the positive scale from [0, max] and the mirrored negative scale from [0, min]).
I know I could create one scale for positive values and one for negative values, but I'm wondering if it's possible to achieve this layout with only one scale. It seems that something like a parabolic scale could help out here (if that exists). Is it possible to achieve something like this in D3?
Before explaining my proposed solution, some considerations about the comments in this question: you cannot use a log scale in your situation. This is an easy mathematical principle: Log(0) is minus infinity. Actually, this is explicitly stated in the docs:
As log(0) = -∞, a log scale domain must be strictly-positive or strictly-negative; the domain must not include or cross zero.
That being said, let's go to the proposed solution.
You could create your own scale (it's not that complicated). However, here, I'll use a interpolate function, based on this excellent answer (not a duplicate, though, because you want the opposite) and this code from Mike Bostock.
Using a linear scale, we set an interpolator:
var xScale = d3.scaleLinear()
.domain([-100, 100])
.interpolate(easeInterpolate(d3.easeQuadInOut));
Then, we use an easing in the easeInterpolate function:
function easeInterpolate(ease) {
return function(a, b) {
var i = d3.interpolate(a, b);
return function(t) {
return i(ease(t));
};
};
}
Here I'm using d3.easeQuadInOut, which I think suits you, but you can change this for another one, or even creating your own.
Have a look at this demo. I'm creating 50 circles, evenly spaced from -100 to +100 (-100, -96, -92, -88... until +100). You can see that they are moved away from the center. If you use this scale with your data, you'll avoid the crowded data points around zero:
var data = d3.range(51).map(function(d) {
return -100 + (d * 4)
});
var svg = d3.select("body")
.append("svg")
.attr("width", 600)
.attr("height", 100);
var xScale = d3.scaleLinear()
.domain([-100, 100])
.range([20, 580])
.interpolate(easeInterpolate(d3.easeQuadInOut));
svg.append("g")
.attr("transform", "translate(0,70)")
.call(d3.axisBottom(xScale));
svg.selectAll(null)
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) {
return xScale(d)
})
.attr("cy", 50)
.attr("r", 4)
.attr("fill", "teal")
function easeInterpolate(ease) {
return function(a, b) {
var i = d3.interpolate(a, b);
return function(t) {
return i(ease(t));
};
};
}
<script src="https://d3js.org/d3.v4.min.js"></script>
In case you ask, that last tick is not 80100. That's just the 80 tick overlapping with the 100 tick (the same thing happens with the -80 and the -100).
Also, it is worth noting that there is nothing wrong in using transformed scales, and that even if it does deform or skew the chart, it's perfect valid and does not lead to misinterpretations, as long as you inform the users about the transformation.
I'm trying to create a simple scatter plot in d3 (similar to this one from matplotlib):
I use extent() to set the scale's input domain range.
xScale.domain(d3.extent(xvalues));
Using this approach results in some dots overlapping axises in d3 plot:
How to avoid axis overlapping and make a margin similar to the matplotlib's plot?
Input values vary, so simple increment / decrement of the extent() output doesn't look like a general solution.
In general, the best way of handling this is to call the scale's .nice() function, which will round the ends of the domain of the scale to nice values. In your particular case, this doesn't work, as the values are "nice" already.
In this case I would compute the extent of the domain and extend it by a fraction of that. For example:
var padding = (xScale.domain()[1] - xScale.domain()[0]) / 10;
xScale.domain([xScale.domain()[0] - padding, xScale.domain()[1] + padding]).nice();
In your matplotlib image, the dots are not overlapping and the x scale has negative value.
In d3:
var xScale = d3.scale.linear()
.domain([
d3.min(data, function(d) {
return d.val;
})-10, //so the domain is wider enough for the zero value
d3.max(data, function(d) {
return d.val;
}),
])
.range([height , 0])
I'm using Mike Bostock's example as a template and building on it. My bar chart here.
After transition to stacked version, I am unable to get the y position of the bars. Bars of higher height overshadow the smaller ones. Most likely because of the valueOffset attribute of the stack. I am stuck on this issue for few days now.
Changes from Mike's example:
removed group labels in stacked chart
new y-axis y2 on linear scale. The domain for this axis is from 0 to the maximum of all the sums of values in each year which is 141.
defined new stack stack_year for relative positions of the bars.
Relevant code:
// y2 definition
y2.domain([0, d3.max(dataByGroup_year, function(d) { return d.year_wise_sum; })]).range([height, 0]);
// calculates sum of all wins per year
dataByGroup_year.forEach(function(d) {
var order = d.values.map(function(d) { return d.value; });
d.year_wise_sum = d3.sum(order);
});
function transitionStacked() {
var t = svg.transition().duration(750),
g = t.selectAll(".group").attr("transform", "translate(0," + y0(y0.domain()[0]) + ")");
g.selectAll("rect").attr("x", function(d) { return x(d.year); })
.attr("y", function(d) { return height - y2(d.valueOffset); })
.attr("height", function(d) { return height - y2(d.value); });
g.selectAll(".group-label").text("");
}
y0 is the ordinal scale used for multiple charts. y1 is the linear scale used for each chart in multiple charts.
Full HTML code at github
Data used: input file. I disabled tips for each bar.
Update: JSFIDDLE
Any help is much appreciated! Thank you
There were a number of issues here, which I've fixed up in this fiddle: http://jsfiddle.net/henbox/wL9x6cjk/4/
Part of the problems was the data itself (as per my comment above). There were some repeated values, which was causing issues when calculating the valueOffset correctly (using the d3.layout.stack)
I've also made some changes to how the y and attribute for each rect are calculated in the transitionStacked function. I changed what you had:
.attr("y", function(d) {
return height - y2(d.valueOffset);
})
to:
.attr("y", function (d) {
return y2(d.value + d.valueOffset) - height;
})
Note that you need to sum the d.value and d.valueOffset, before applying the scaling, to calculate the top left corner position of the rect. Additionally, you don't need to recalculate the x attribute value since this doesn't change between the two chart views, so I removed it
I also removed the call to stack_year(dataByGroup_year);. You don't need to build the stack layout here, just to calculate the maximum sum per year.
Finally I also tidied up the y-axis positioning a bit so there's enough space for the x-axis labels, and simplified the positioning of group elements in the stacked view. I also moved the x-axis to be appended to svg rather than group, which simplified positioning of elements