d3 chart - get x axis time of max value - javascript

I have a array of values [date, value].
I can find the max value with:
max_value = d3.max(data_array, function(d) { return d[1]; } );
How do I get the d[0] value of d[1] maximum so I can mark the position of maximum value on chart?

Use javascript array filter function.
var max_value = d3.max(data_array, function(d) { return d[1]; } );
var requiredArr = data_array.filter(function(d){
return d[1]==max_value;
});
var dateWithMaxValue = requiredArr[0];

If you don't care about performance too much, here's a one-liner:
// d is [x, y] where x is what you're looking for
var d = data_array.sort(function (a, b) { return b[1] - a[1]; })[0];
Here's a simple demo.

Related

D3: Passing extra arguments to attr functions inside a selection.join()

I've some code inside a selection.join() pattern:
const nodeWidth = (node) => node.getBBox().width;
const toolTip = selection
.selectAll('g')
.data(data)
.join(
(enter) => {
const g = enter
.append('g')
g.append('text')
.attr('x', 17.5)
.attr('y', 10)
.text((d) => d.text);
let offset = 0;
g.attr('transform', function (d) {
let x = offset;
offset += nodeWidth(this) + 10;
return `translate(${x}, 0)`;
});
selection.attr('transform', function (d) {
return `translate(${
(0 - nodeWidth(this)) / 2
},${129.6484} )`;
});
},
(update) => {
update
.select('text')
.text((d) => d.text);
let offset = 0;
update.attr('transform', function (d) {
let x = offset;
offset += nodeWidth(this) + 10;
return `translate(${x}, 0)`;
});
selection.attr('transform', function (d) {
return `translate(${
(0 - nodeWidth(this)) / 2
},${129.6484} )`;
});
}
);
as you can see, in the enter and update section I need to call a couple of functions to calculate several nodes transformations. In particular, the code stores in the accumulation var offset the length of the previous text element. This properly spaces text elements (ie, text0 <- 10 px -> text1 <- 10 px -> ...).
As you can see, the "transform functions" in the enter and update section are identical. I'm trying to define them just in one place and call them where I need. E.g.,
(update) => {
update.attr('transform', foo);
selection.attr('transform', bar);
}
However, I cannot refactor the code this way because it looks like I cannot pass in neither the offset value nor this to the function passed to attr().
Is there a way to do it?
EDIT:
As per Gerardo Furtado's hint (if I got it right), you can define foo as follows:
const foo = function(d, i, n, offset) {
let x = offset;
offset += nodeWidth(n[i]) + 10;
return `translate(${x}, 0)`;
}
then in the selection.join¡ you have to call foo this way:
(update) => {
let offset = 0;
update.attr('transform', (d, i, n) => foo(d, i, n, offset));
}
However, refactoring this way, offset is ever equal to 0. A possibile solution here: https://stackoverflow.com/a/21978425/4820341
Have a look at Function.prototype.bind().
const doSomething = (d) => {
return `translate(${
(0 - nodeWidth(this)) / 2
},${129.6484} )`;
}
Calling the function inside (enter) and (update)
selection.attr('transform', doSomething.bind(d));
This way the function gets executed in the current scope.
I guess this is what you are looking for. Please be aware that I could not test my code!

Is it possible to make offset/shift in d3.js scales?

I want to make an "overflow" scale that will reset value and continue count from zero if it overflows limit
For example, I want to draw a chart but change the original 0 point place. The first column represents a normal scale, and in the second column I shifted the scale
5 2
4 1
3 0
2 5
1 4
0 3
I think I can alter input data and convert first half of values to negative so the domain will look like this [-2: 3]. But I would prefer not to alter input data values and just have a parameter in scale that can do this for me.
UPD:
I've tried to use more than 2 domain/range values - didn't give me the results that I want, so I wrote a custom interpolation function.
I doubt that only I need something like this and probably d3.js provide something similar. If you know a function like this in standard d3.js distribution please point me to it.
const svg = d3.select("svg").attr('height', 200);
myinterpolate = function(a, b) {
var a = a;
var b = b;
var shift = 100;
var max_value = Math.max(a, b)
var min_value = Math.min(a, b)
return function(t) {
let result = a * (1 - t) + b * t + shift;
if (result < min_value) {
result = result + max_value
} else if (result > max_value) {
result = result % max_value
}
return result
}
;
}
const myScale = d3.scaleLinear().domain([0, 10]).range([200, 0]).interpolate(myinterpolate);
const axis = d3.axisLeft(myScale).tickValues(d3.range(10)).tickFormat(d=>~~d)(svg.append("g").attr("transform", "translate(50,0)"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg><svg>
You said "I would prefer not to alter input data values and just have a parameter in scale that can do this for me", but that's a very specific and highly unusual use case, so there is none.
What you could easily do is creating a piecewise scale with more than 2 domain/range values:
const svg = d3.select("svg");
const myScale = d3.scaleLinear()
.domain([0, 3, 4, 6])
.range([75, 6, 144, 98]);
const axis = d3.axisLeft(myScale).tickValues(d3.range(7)).tickFormat(d => ~~d)(svg.append("g").attr("transform", "translate(50,0)"))
path {
stroke: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg></svg>
This simply answers your question as it is. Mind you that it raises several other questions: where are you drawing the values between 3 and 4? What's the meaning of the area between 6 and 0?
A custom interpolation function worked for me.
My code snippet uses a hardcoded value for the shift, but it can be rewritten to use shift value as a parameter.
const svg = d3.select("svg").attr('height', 200);
myinterpolate = function(a, b) {
var a = a;
var b = b;
var shift = 100;
var max_value = Math.max(a, b)
var min_value = Math.min(a, b)
return function(t) {
let result = a * (1 - t) + b * t + shift;
if (result < min_value) {
result = result + max_value
} else if (result > max_value) {
result = result % max_value
}
return result
}
;
}
const myScale = d3.scaleLinear().domain([0, 10]).range([200, 0]).interpolate(myinterpolate);
const axis = d3.axisLeft(myScale).tickValues(d3.range(10)).tickFormat(d=>~~d)(svg.append("g").attr("transform", "translate(50,0)"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg><svg>

How to set y axis (yDomain) value to max in D3 Js

In My D3 chart, How to set yDomain to the max value. Any suggestions ?
If we see on nov 10 my line and area is going out of the box.
My Code sandbox here
I am calculating the two domains separately for yDomainMagnitude and yDomainStartupMagnitude, but now how to consolidate or take the union of both and assign to yDomain.
var yDomainMagnitude = d3.extent(data, function(d) {
return d.magnitude;
});
var yDomainStartupMagnitude = d3.extent(data, function(d) {
return d.startupMagnitude;
});
var yDomain = yDomainStartupMagnitude; // here I have to have union of both and assign.
var xScale = d3
.scaleTime()
.range([0, width])
.domain(xDomain);
var yScale = d3
.scaleLinear()
.range([height, 0])
.domain(yDomain);
There are several ways to calculate the yDomain in place. Yours does obviously work, it can be significantly simplified, though:
var yDomain = [
d3.min(data, d => Math.min(d.magnitude, d.startupMagnitude)),
d3.max(data, d => Math.max(d.magnitude, d.startupMagnitude))
];
This approach checks whatever value magnitude or startupMagnitude is smaller / greater using Math.min() / Math.max(), respectively, for any data point. It then utilizes d3.min() and d3.max() to calculate the global extent over those values.
As you have a relational condition inside your function, it'll always take the larger value. You could instead calculate the two domains separately for startupMagnitude and magnitude, and then union the extents (take the minimum and maximum of the minimum and maximum extent values, respectively).
const magnitude = d3.extent(data, d => d.magnitude);
const startup = d3.extent(data, d => d.startupMagnitude);
const yDomain = [
d3.min([magnitude[0], startup[0]]),
d3.max([magnitude[1], startup[1]])
];
Or, a single-assignment version, exploiting that you just union the extents:
const yDomain = d3.extent([
...d3.extent(data, d => d.magnitude),
...d3.extent(data, d => d.startupMagnitude)
]);
Yet another is to union the data first, then compute the extent:
const yDomain = d3.extent([
...data.map(d => d.magnitude),
...data.map(d => d.startupMagnitude)
]);
These don't mix the D3 way of calculating min/max/extent with the different semantics of Math.min/Math.max, eg. handling of strings, nulls, and results if the array is empty. In this regard, the last version is the most faithful to the D3 way, as it uses a single d3.extent.
This is how I am calculating the two domains separately for startupMagnitude and magnitude, and then comparing the max and min and creating the yDomain.
I am not sure this is correct or any other d3 method is there,
var yDomainMagnitude = d3.extent(data, function(d) {
return d.magnitude;
});
var yDomainStartupMagnitude = d3.extent(data, function(d) {
return d.startupMagnitude;
});
var max =
d3.max(d3.values(yDomainMagnitude)) >
d3.max(d3.values(yDomainStartupMagnitude))
? d3.max(d3.values(yDomainMagnitude))
: d3.max(d3.values(yDomainStartupMagnitude));
var min =
d3.min(d3.values(yDomainMagnitude)) <
d3.min(d3.values(yDomainStartupMagnitude))
? d3.min(d3.values(yDomainMagnitude))
: d3.min(d3.values(yDomainStartupMagnitude));
var yDomain = [min, max];

Pulling values with javascript using d3.nest for force directed graph

I am trying to add some summary statistics to my force directed D3 graph. I can see the array with console.log(d), but when I try to use the simplest javascript I can't seem to pull the values from the array. Here is the pertinent code:
function fade(opacity) {
return d => {
node.style('stroke-opacity', function (o) {
const thisOpacity = isConnected(d, o) ? 1 : opacity;
console.log(d)
var new_data = d3.nest()
.key(function(j) { return j.group;})
.rollup(function(j) {
return d3.sum(j, function(g) {return g.contribution_total; });
}).map(d); //.entries(d);
//new_data
console.log(new_data)
var expensesByName = d3.nest()
.key(function(g) { return g.group; })
.entries(d);
//.forEach(node);
console.log(expensesByName )
var contributionsByName = d3.nest()
.key(function(g) { return g.contribution_total; })
.entries(d);
//.forEach(node);
//console.log(contributionsByName)
Here is where the live attempt lives: http://people.ischool.berkeley.edu/~jennifer.p/capstone/slider/add_winner_try1.html
All of my console.log() attempts (besides console.log(d)) return an empty array. See picture:
Ultimately I am trying to show a total contribution amount by group (democrat/republican) and then I want to calculate a 'winner' percentage (# nodes with 'W' vs # with null), but right now I just really need help accessing the data period. I've been at this a few hours and it seems so simple, any thoughts are appreciated. TIA!
you need to collect the nodes connected inside the opacity callback and after setting use the list to calculate the d3.next
function fade(opacity) {
return d => {
var connectedNodes = [];
node.style('opacity', function (o) {
var connected = isConnected(d, o);
if (connected) { connectedNodes.push(d); }
return connected ? 1 : opacity;
});
var new_data = d3.nest()
.key(function(j) { return j.group;})
.rollup(function(j) {
return d3.sum(j, function(g) {return g.contribution_total; });
}).entries(connectedNodes);
console.log('new_data', new_data);
link.style('opacity', o => (o.source === d || o.target === d ? 1 : opacity));
};
}
Why do you have a click-handler with fade(1) when you also have the releasenode handler?
.on('click.fade', fade(1))
.on('click',releasenode)

d3.js t.map is not a function

Hope somebody can help me out because I can't find any reference about this error.
I was working on this piece of code:
var xMin = d3.min(data, function(d) { return d.value; });
var xMax = d3.max(data, function(d) { return d.value; });
if (0 > xMin & 0 > xMax) {
xMax = 0;
}
if (0 < xMin & 0 < xMax) {
xMin = 0;
}
x.domain(xMin, xMax).nice();
y.domain(data.map(function(d) { return d.label; }));
but I must have made some mistake cause now the loading blocks with the error message below in the web console:
"TypeError: t.map is not a function # http://d3js.org/d3.v3.min.js:2
.domain() takes an array as argument, i.e.
x.domain(xMin, xMax).nice();
should be
x.domain([xMin, xMax]).nice();
I had this error when I switched the mock data from an example.
var dataset = d3.layout.stack()(["CountPending", "CountDenied"].map(function (type) {
return data.map(function (d) {
return { x: d.Name, y: +d[type] };
});
}));
In my dataset the example data was using ["pending","denied"] while my real data used the following keys ["CountPending", "CountDenied"]
Use the right keys!
While this might not help the OP, I hope it helps someone.

Categories