Change the Y axis range based on the selected data - javascript

I have a chart done in the D3. It works but I would like to add the ability of the axis scaling.
I have several sets of data that have a completely different range.
So I would like the Y axis range to be altered according to the selected series.
This is the entire code: https://plnkr.co/edit/89ERO3GuxREvYQ3DRX5L?p=preview
For the moment the example linked has 4 series: I would like that if for example the user selects "Kiwi" and "Apple" then the Y axis has range [0-1] and not [0-15] (or something like that).
The important thing is that the range is changed according to the selected data.
An example of what I mean is http://bl.ocks.org/andrewleith/3824854.
To do so I thought to save in an array the tags that have opacity 0.5 or 1 (the visible lines) and then according to that change the domain Y.
I created the array, and then I tried to identify the tag with opacity 0.5 (for simplicity) in this way:
var actives = new Array();
var activeElem = d3.select(".line").attr("opacity", 0.5);
console.log(activeElem);
or
var actives = new Array();
var activeElem = d3.selection.style({opacity: "0.5"});
console.log(activeElem);
or
var actives = new Array();
var activeElem = d3.selectAll("path[opacity='0.5']");
console.log(activeElem);
None of these works.
I also did other tests but I don't remember exactly what, and still did not work.
I am going crazy.
What is the right way to select items with opacity 0.5 and 1 and change the Y-axis based on the selected data?
Thank you

Hi the main issue here is that you need to make another calculation of the min and max values of your data and afterwards update your yAxis with the new domain with the min and max values.
// find the new max value of the selected series
var max = d3.max(filteredData, function(d) {
return d3.max(d.values, function(e) {
return e.value;
});
});
// find the new min value of the selected series
var min = d3.min(filteredData, function(d) {
return d3.min(d.values, function(e) {
return e.value;
});
});
// set the new y domain
y.domain([min, max]);
// update our new y axis with the new domain
svg.selectAll("g .y.axis").transition()
.duration(800).call(yAxis);
Since you want to filter your data by selecting certain lines I recommend using the data function which will allow you to join the specified array of data with the current selection (more info here). You are making a huge forEach loop when you could be using the d3 utilities. Something in the lines of:
// apend a group element which will contain our lines
svg.append('g')
.attr('class', 'line-container')
.selectAll('.normal-line-paths')
.data(dataNest) // set our data
.enter() // enter the data and start appending elements
.append('path')
.call(path); // calling our path function for later use when appending lines
I modified your code to use a more document driven approach, you can click on the labels and the y axis will be scaled accordingly t the new min and max values of the filtered dataset. I left the dashed group in blank so you can try and code the remaining parts and understand the logic behind the data join.
https://plnkr.co/edit/Wso0Gu4yHkdsHhl8NY8I?p=preview
EDIT:
Here is the plnkr with the dashed lines :)
https://plnkr.co/edit/yHGGtrLXZyyq0KpiVWYf?p=preview

Related

D3js v5 Trying to select multiple bars in bar chart using brush and saving values to variable and table

I have a bar chart code, wherein a user can select bars from a bar chart to populate a tabulated list. Since a lot of the data has values close to 0, it becomes difficult for users to click on those bars. In order to circumvent this issue, I made invisible bars which also hold the data, which have the height of the entire chart area. By using mouse over and click on these invisible bars I am able to let users highlight the bars and select them to populate a table list. The values also get saved to a object for further processing later. I was able to add zoom functionality as well.
Now the issue: I would like the user to be able to select multiple of these invisible bars and pass multiple values into the object. I have tried to use the brushX feature of d3js v4-v5 to let the user be able to highlight areas of interest for selection (following this v3 code).
However, I cannot figure out how to get the values from the selected area. Currently it is returning empty arrays.
Here is some of the code for the brush
var brush = d3.brushX();
svg.append("g")
.attr("class","x brush")
.call(brush)
.selectAll("rect")
.style({
"fill": "#69f",
"fill-opacity": "0.3"
});
brush.on('brush', function(d){
k = brush.extent();
j = data.filter(function(d){
return k[0] <= d.Name && k[1] >=d.Name;
});
console.log(j);
});
Update: Because plnkr is acting very weird I have made a block with the same code:
https://bl.ocks.org/Coola85/f20f66ee905d13880fc464d75b571603/
Here is the plunk with the code for those who prefer plnkr.co
You must click Add Chart to see the Chart.
Please let me know if you need more information.
Since I am new to D3js and SO, I would like to apologize for naivety.
brush.extent() without argument now returns the function that calculates the extent, not the brush’s position. Use d3.event.selection instead.
Once you have the selected extent, compare it to your x scale:
brush.on('end', function(){
var k = d3.event.selection; // extent of the brush, [x0, x1] since it's an x-brush
var j = data.filter(function(d) {
var x0 = x(d.ID), // start of the corresponding band
x1 = x0 + x.bandwidth(); // end of the corresponding band
// check if the start of the brush is before the band
// and if the end of the brush is after the end of the band
return k[0] <= x0 && k[1] >= x1;
});
console.log(j);
});

How to use scatter chart with complex data?

I trying to implement scatter chart by example.
In example we can see creating dimension:
runDimension = ndx.dimension(function(d) {return [+d.Expt, +d.Run]; });
Example's data:
Expt Run Speed
1 1 850
1 2 740
1 3 900
I want to use same chart, but I have my data in next format:
[
{
"Timestamp":"2016-12-15T17:29:53Z",
"idgame":"euro",
"users":{
"Jo": {
"energy":200,
"jump_height":0.5
},
"Bob": {
"energy":220,
"jump_height":0.35
}
}
},
{
"Timestamp":"2016-12-15T17:29:55Z",
"idgame":"euro",
"users":{
"Jo": {
"energy":120,
"jump_height":0.15
},
"Bob": {
"energy":240,
"jump_height":0.75
}
}
}
]
I need to build next chart, where x-axis is timestamp and y-axis is jump_height:
My data is allready in crossfilter, so I can't change it.
How can I create good dimension with current format?
I'm still not convinced this is worth the effort, versus biting the bullet and flattening your data and fixing up the other charts. If your data isn't flat you will be fighting crossfilter and dc.js every step of the way.
That said, as usual, it's possible!
We can't use the series chart, because that requires all the data to be present in one group. But since you want to produce multiple symbols from each row of data, an ordinary crossfilter group can't produce the data you need.
Maybe we could use a fake group, but that would be complicated. Instead, let's produce a dimension and group for each user, and then sew them together using a composite chart.
First, we need to parse those timestamps:
data.forEach(function(d) {
d.Timestamp = new Date(d.Timestamp);
});
Next, we'll retrieve a list of all users, by pulling the user keys from each event (timestamp), concatenating them, and then using a d3.set to unique them:
// retrieve all users from all events
var users = data.reduce(function(p, event) {
return p.concat(Object.keys(event.users));
}, []);
users = d3.set(users).values();
In the rest of the code, we'll assume there are the same users for each event. It's possible for them to be different, but it adds extra complexity, and this answer is complicated enough. Just ping me me if you need that feature!
We'll create the chart, crossfilter, and a scale which will assign symbols to users:
var chart = dc.compositeChart("#test");
var ndx = crossfilter(data);
var symbolScale = d3.scale.ordinal().range(d3.svg.symbolTypes);
Now we can create the composite chart. (We'll add the scatter subcharts in the next step.)
chart
.width(768)
.height(480)
.x(d3.time.scale())
.xUnits(d3.time.seconds)
.elasticX(true)
.elasticY(true)
.brushOn(false)
.clipPadding(10)
.shareTitle(false) // allow default scatter title to work
.shareColors(true) // but use different colors for subcharts
.legend(dc.legend().x(350).y(350).itemHeight(13)
.gap(5).horizontal(1).legendWidth(140).itemWidth(70));
We set up the X axis with a time scale, with a resolution of seconds. Both axes have elastic. We need to share colors so that each subchart will be assigned its own color. (The legend is perhaps overspecified - I copied this from another example.)
Finally we get to the meat of it. For each user, we'll create a subchart, and we'll tell the composite chart to compose all of them:
chart.compose(users.map(function(user) {
var userDimension = ndx.dimension(function(d) {
return [d.Timestamp, d.users[user].jump_height];
})
var jumpGroup = userDimension.group();
console.log(user, jumpGroup.all());
var scatter = dc.scatterPlot(chart)
.symbol(symbolScale(user))
.dimension(userDimension)
.group(jumpGroup)
.colorAccessor(function() { return user; })
.symbolSize(8)
.highlightedSize(10);
return scatter;
}))
We're creating a new dimension for each chart. This is because the dc.js scatter plot expects the keys to include both X and Y coordinates, and we can only access the Y coordinate (jump_height) once we know the user. Once we get past that, the group is simple.
The chart will assign the symbol and color based on the user key. These both work the same; an ordinal scale will assign a new value from the range for each new value it encounters in the domain. The only difference is that we're using the default color scale, whereas we had to specify our own symbol scale.
Here's a fiddle: https://jsfiddle.net/gordonwoodhull/3m4mv3xf/19/

How to change sizing of D3 axis?

Working within a React framework, I have a JSON data set of weights and I am trying to display on a simple number line using D3.js, where the left end is the min, the right end is the max, and a particular individuals weight is a tick with the color red.
I've coded up a solution that creates this, however I cannot for the life of me figure out how to make it larger. Here's the code, where this.state.range = [126.4, 212.2] and this.state.avg = 167:
var svgContainer = d3.select("body").append("svg").attr("width",500).attr("height", 200);
var axisScale = d3.scaleLinear().domain(this.state.range).range(this.state.range);
var axisBot = d3.axisBottom(axisScale);
var ticks = [this.state.range[0], this.state.range[1], this.state.avg, this.state.patient.weight];
axisBot.tickValues(ticks);
var xAxisGroup = svgContainer.append("g").call(axisBot);
d3.selectAll('g.tick').filter(function(d){ return d==186.4;} ).select('line').style('stroke','red');
This code produces this result:
Can anyone please tell me how to a) make this number line larger and b) how to center the line?
I apologize if this is a bit remedial, however it is my first time working with D3.js.
In your code, the domain and the range are the same (assuming that this.state.range is an array):
var axisScale = d3.scaleLinear()
.domain(this.state.range)
.range(this.state.range);
You have to set the range according to the positions you want in your SVG. For instance, without any padding, if your SVG has a width of 500px:
var axisScale = d3.scaleLinear()
.domain(this.state.range)
.range([0, 500]);
So, the range goes from left border (x = 0) to the right border (x = 500).
Remember: domain refers to the input values, while range refers to the output positions.

d3.js Force layout only applied vertically

I'm doing a data visualisation with d3. To give you some context,
the graph contains about 400 nodes (all data is loaded from multiple
json files) that are connected to each other
they are all mapped by year in a timeline (x axis)
the position in the y axis is completely randomized
the nodes have all different sizes
Now my question:
How can I distribute the nodes in the y axis so that they don't overlap?
You can checkout the full sourcecode on the GitHub Repository (work in progress - currently on the real-database branch).
This is a screenshot of how it currently looks:
Basically, in the tick() function, reset the nodes array x values to what you want them to be (presumably some scale to do with year), and the node and links will be drawn at those x values, and subsequent force calculations will start again from those values too
force.on("tick", function() {
// Adjust to what you want nodePos to be, here I'm just doing it by index
graph.nodes.forEach (function(nodePos,i) {
nodePos.x = i * 15;
//nodePos.x = xscale (data[i].year); // or whatever
})
// then normal node/link layout
I've forked this standard force-directed example by blt909 to show one way it could be done -->
http://jsfiddle.net/vztydams/
PS If you have a lot of items and very few discrete x values, best to give them a bit of wiggle room at first (i.e. a range in x they're contained to rather than a value), then slowly narrow that range down. Otherwise nodes will get 'stuck' behind each other.
Edit 02/03/16:
Hi Alvaro, essentially the graph.nodes is your linked data, as these are the objects that are attached to the displayed nodes as the data.
So if I set up a scale, and stick in a random year per datum:
var dom = [1994,2014];
var xscale = d3.scale.linear().domain(dom).range([20,400]);
graph.nodes.forEach (function(datum) {
datum.year = dom[0] + Math.floor (Math.random() * (dom[1] - dom[0]));
});
...
We can then restrict the x position of each node's datum like this:
graph.nodes.forEach (function(d,i) {
//d.x = i * 15;
d.x = xscale(d.year);
})
(As I say, if you have a lot of nodes and few years, you'd be better restricting to a range and then narrowing that range down on each subsequent tick)
http://jsfiddle.net/vztydams/2/

D3 bar chart from objects with arrays

Good afternoon.
I am using this D3 bar chart example with my data (that is in the same format whereof in the
jsfiddle example).
The chart works well but i have some problems that i do not know i solve.
The xAxis have the number of index of the key parameter and not the key parameters values;
When I sort the data after pressing the "sort" button;
The numbers over the bars after the sort disappear or appear the the left side insted of over the correspondent bars
var dataset = {key: [0, 30], value:[60, 30]};
http://jsfiddle.net/3HL4a/75/
Here is the link where you can see the code and perform the changes.
Thanks
To reference the key values, you have to use those instead of the index:
var xScale = d3.scale.ordinal()
.domain(dataset.key)
.rangeRoundBands([0, w], 0.05);
// ...
.attr("x", function(d, i) {
return xScale(dataset.key[i]);
})
You can make this nicer by merging the key and value arrays such that both are part of the same data element.
As for the other problem, this is because you're selecting text elements, which include the axis labels. Even with the key function this doesn't work here, as one of the axis labels is the same as the label you want to display on the bar. Assigning a special class to these labels and selecting accordingly fixes this.
See here for the complete jsfiddle.

Categories