How to change sizing of D3 axis? - javascript

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.

Related

Why is d3.histogram merging the last two bins of this chart?

In this D3 chart I'm working on, I have a band scale containing 60 elements in a non-linear pattern (101-110; 201-210; 301-310; 401-410; 501-510 and 601-610 — the first digit represents the season and the last two digits represent the last two digits).
I'm able to render the chart perfectly fine, but the last bar gets merged into the one before it:
(Ignore the fact the x-axis labels are all messed up.)
Any idea why this is happening? Is it just because I've totally perverted the meaning of a histogram, or what?
Full example code here.
You are correct: you totally perverted the meaning of a histogram! This should be a bar chart instead.
Bar charts, by their very nature, are made of bars representing a categorical variable. It means that the bars are positioned over a label that represents a categorical variable, i.e., a qualitative variable. Both bar charts and histograms use rectangles to encode data, but in a histogram, unlike a bar chart, the label represents a quantitative variable. It's very common, if you search the web, finding "histograms" which are in fact bar charts, or "bar charts" which are in fact histograms.
Back to your question: It's possible to keep your histogram, in a hacky solution: you just have to define the domain:
If domain is specified, sets the domain accessor to the specified function or array and returns this histogram generator.
So, your histogram generator should be something like this:
const histogram = d3.histogram()
.value(d => d.death)
.domain([0, 1000])//in your case: [101 or less, more than 610]
.thresholds(x.domain());
(I'm using magic numbers here just to show the idea, change them accordingly)
And here is the updated code: https://www.webpackbin.com/bins/-KgCh8IjK6J56Dj_SA9X
Analysis
Apparently, d3.histogram is not creating the last bin when the value coincides with the threshold. According to the docs the last bin should be created, since x1 (the top value for each bin) is not inclusive.
Let's see this snippet:
var data = d3.range(10);
const histogram = d3.histogram()
.value(d => d)
.thresholds(data);
var bins = histogram(data);
console.log(bins)
<script src="https://d3js.org/d3.v4.js"></script>
You can see that 8 and 9 were placed together in the last bin.
The same thing doesn't happen with the other thresholds. First, thresholdFreedmanDiaconis:
var data = d3.range(100);
const histogram = d3.histogram()
.value(d => d)
.thresholds(d3.thresholdFreedmanDiaconis(data, d3.min(data), d3.max(data)));
var bins = histogram(data);
console.log(bins)
<script src="https://d3js.org/d3.v4.js"></script>
Then, thresholdScott:
var data = d3.range(100);
const histogram = d3.histogram()
.value(d => d)
.thresholds(d3.thresholdScott(data, d3.min(data), d3.max(data)));
var bins = histogram(data);
console.log(bins)
<script src="https://d3js.org/d3.v4.js"></script>
Finally, thresholdSturges:
var data = d3.range(100);
const histogram = d3.histogram()
.value(d => d)
.thresholds(d3.thresholdSturges(data, d3.min(data), d3.max(data)));
var bins = histogram(data);
console.log(bins)
<script src="https://d3js.org/d3.v4.js"></script>

Is there a way to force the maximum in an amChart to be less than the maximum data value?

I have data that is very skewed and because of one outlier the whole graph zooms out.I want to force the min-max values of the y axis ,so that most of the data in the graph is properly scaled.The outliers can go missing or be equal to the max/min values.They are not important.
Is there a way to do this without actually manipulating the data that is used as input for the chart ?
This is not the actual graph(I'll be using a line graph).
Fiddle
var valueAxis = new AmCharts.ValueAxis();
valueAxis.axisAlpha = 0.5;
valueAxis.minimum = 00;
valueAxis.maxumum=80;
valueAxis.strictMinMax=true;
valueAxis.dashLength = 3;
chart.addValueAxis(valueAxis);
Yes it's possible. You misspelled maximum as "maxumum", so your code doesn't work.
valueAxis.maximum = 80
Here's your updated fiddle.

Change the Y axis range based on the selected data

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

Optimize pendulum animation (javascript)

TL;DR: I wrote plotly-based javascript simulation of mathematical pendulum. It works very slow. I'm looking for ideas on how to optimize it. Currently trying "bare" d3.js and struggling from the problem of coordinate transformation between SVG's coordinates and my own logical coordinates.
I'm writing web-textbook on ordinary differential equations and want to include interactive simulation and visualization of mathematical pendulum. Visualization should contain pendulum itself, its potential energy graph and full energy contour plot. Then user can choose the initial condition by clicking on energy contour plot, then animation should begin showing how the point moves in the phase space.
I wrote an example of such simulation:
https://jsfiddle.net/ischurov/p1krqnt6/
It's plotly-based. I create three axes and put there necessary graphs. The point that represent current state of the system is also a graph in plotly (i.e. scatter plot with the only one point).
Animation works as follows: I get current coordinates of the point in the phase space, calculate the new position of this point after some time, then update my graph according to this new position. The corresponding code:
var div = document.getElementById('myDiv');
function updateState(phi, v) {
var update = {x: [[phi], [phi], [0, Math.sin(phi)]], y: [[v],
[PotentialEnergy(phi)], [0, -Math.cos(phi)]]};
Plotly.restyle(div, update, [phaseDotIndex, 3, 4]);
}
myPlot.on('plotly_click', function(data){
if(data.points[0].data.type == 'contour'){
updateState(data.points[0].x, data.points[0].y);
}
});
var animate = null;
$('.animate_button').click(function(){
var div = document.getElementById('myDiv');
if(animate === null) {
var phi = div.data[phaseDotIndex].x[0],
v = div.data[phaseDotIndex].y[0],
E = FullEnergy(phi, v);
animate = setInterval(function() {
var phi = div.data[phaseDotIndex].x[0],
v = div.data[phaseDotIndex].y[0],
step = 0.1, newphi, newv, update;
newphi = phi + v * step;
newv = v + step * Force(phi);
/* skip some tweaks here */
updateState(phi, v);
},
100)
}
else
{
clearInterval(animate);
animate = null;
}
}
This code works almost as expected, but really slow and not smooth — at least, under Firefox (If I decrease update interval it works even worse).
I'm looking for ways to optimize this.
I believe that performance problems are due to plotly's update process: in order to move one point it have to recalculate the whole picture and it is slow. So I'm looking for ways to do it in different way.
Are there any ideas?
I'm looked for some direct d3.js approach which can be faster. I see the following steps here:
Draw a graph of potential energy and contour plot of full energy.
Draw the pendulum itself.
Put small circles on the graphs of potential energy and contour plot.
Make 'onclick' event handler to allow user to choose the initial state.
Run animation loop by updating the position of the circles and the pendulum according to current state.
To proceed with step 1, I can use third-party d3.js libraries like conrec for contour plots and/or excellent maurizzzio's function plot or even plotly itself (but I'm not going to use plotly to update the graph). Step 2 seem to be doable, but I didn't try it yet. The most difficult for now are steps 3 and 4 as I don't understand how to transform SVG coordinates into my graph's coordinates (that are plotted with some library) and vice-versa.
Or maybe there are more simple ways to do it?
I'm the author of function plot which is built on top of d3, luckily d3 has methods to perform mappings in d3-scale so assuming that you have a canvas of width x height dimensions which should be mapped linearly to the rectangle [xMin, yMin] x [xMax, yMax] in 2D euclidean space you'd need to create two linear scales
var xScale = d3.scale.linear()
.domain([xMin, xMax])
.range([0, width])
var yScale = d3.scale.linear()
.domain([yMin, yMax])
.range([height, 0])
Note that in SVG the y axis is flipped and because of that the yScale range's flipped too, then any 2D euclidean point is transformed to SVG coordinates as follows
var xCanvas = xScale(point.x)
var yCanvas = yScale(point.y)
The inverse transformation is given by
var xLogical = xScale.invert(point.x)
var xLogical = yScale.invert(point.y)
A possible solution I wrote to your problem using the above is
var instance = functionPlot({
target: '#demo',
disableZoom: true,
data: [{
fn: 'sin(10*(-cos(x) + y^2/2-1))',
fnType: 'implicit'
}]
})
var xScale = instance.meta.xScale
var yScale = instance.meta.yScale
var canvas = instance.canvas
var circle = canvas.append('circle')
.attr("r", 5)
.style("fill", "purple")
var start = Date.now()
function animate() {
// move the point along the circle of radius 1
var t = (Date.now() - start) * 0.003
var xLogical = Math.cos(t)
var yLogical = Math.sin(t)
var xCanvas = xScale(xLogical)
var yCanvas = yScale(yLogical)
circle
.attr('cx', xCanvas)
.attr('cy', yCanvas)
requestAnimationFrame(animate)
}
animate()
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://maurizzzio.github.io/function-plot/js/function-plot.js"></script>
<div id="demo"></div>
Issue in function plot's GitHub
I realise that this isn't exactly what you want but it does demonstrate that your code can be compact and still do what you require.
The following is some code that demonstrates some maths to animate a pendulum, extracted from one of my javascript widgets but the math logic should still be usable in your own project.
Create an image object - pendulumSet, a nice round ball will do.
// variables set for the pendulum
var gravity = -0.0110808; // tuned to -0.110808 as it approximates a 1 second interval
var acceleration = 0.1; //0.1
var velocity = 0.18; //.18
var angle = 8; // 8 (.4 radians = 22.91 degrees)
Create a timer with an interval of 0.01 seconds
Put this in the timer:
acceleration = gravity * angle;
velocity += acceleration;
angle += velocity;
pendulumSet.rotation = angle +180; // rotation as per a widget engine
That's it...
You might have to use something like the following to achieve the rotation in native javascript:
pendulumSet.style.transform = "rotate(90deg)"; // change the 90deg
This code above will simulate a pendulum in a much more compact fashion. This is 1/100th of a second, graphic animation and mathematical calculation so it will take some cpu, that is unavoidable. Nevertheless, the code is compact and the impact is minimal. Depending upon the engine interpreting the code, the cpu usage will be approx. 20-25% of a 2.5ghz core2duo from 2009, easily handled by more modern, faster cpus. Running similar code in Firefox may be noticeably slower as Firefox, in my experience, seems to perform similar animations less efficiently. You just have to try it and see.
The code is taken from this example javascript widget:
A steampunk clock for the desktop
So you can see the exact same code in operation.

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/

Categories