Error on horizontal Bar chart using d3.js - javascript

I have created a horizontal bar chart using d3.js and every thing works fine if no identical columns values(here it is 'response' field) occurs.
eg: var data = [{
"answer": "Answer2",
"response": 5,
"total": 7,
"color": "#ff4700"
}, {
"answer": "Answer3",
"response": 5,
"total": 7,
"color": "#0ED5EE"
}, {
"answer": "Answer4",
"response": 1,
"total": 7,
"color": "#31EE0E"
}];
If we provide same value on 'response' field( say repeating 5) , the resultant bar count will be not matched as expected, otherwise it will draw perfectly.
Can you please help me to sort out this issue.
Thanks and regards

For y domain give unique name in your case answer:
.domain(data.map(function(d) {
return d.answer;
}));
Then for the y axis give tick format:
.tickFormat(function(d, i){
var d = data[i];
var percentage = parseFloat( (d.response/d.total) * 100).toFixed(1)
return percentage + '%'; })
working code here

The y axis domain value must be unique for it to work properly.
So your response value is not unique and in theory your answer does not have to be unique either. Furthermore, using answer as domain and then getting the data by index seems dirty and may cause unexpected results when (re-)sorting the data.
To get unique values for your y domain you could add an unique id to each of your data objects e.g.:
var data = [{
"uniqueId": 1,
"answer": "Answer2",
"response": 5,
"total": 7,
"color": "#ff4700"
},....]
y.domain(data.map(function(d) { return d.uniqueId; }));
And then add a tickFormat to the yAxis:
yAxis.tickFormat(function(id, i){
var d = data.find(function(d) { return d.uniqueId === id; });
var percentage = parseFloat( (d.response/d.total) * 100).toFixed(1)
return percentage + '%';
})
result: https://jsfiddle.net/rd8z5k32/1/
Alternatively you could just use the indexes of the data array as domain:
y.domain(d3.range(data.length));
yAxis.tickFormat(function(_d, i){
var d = data[i];
var percentage = parseFloat( (d.response/d.total) * 100).toFixed(1)
return percentage + '%';
})

Related

Getting two random non consecutive and unique elements from an array

Hi I am trying of getting two unique random elements from a list that are not consecutive. The array is formed as following
[
{"id": 1, "name": "Monday", "workers": []},
{"id": 2, "name": "Tuesday", "workers": []},
{"id": 3, "name": "Wednesday", "workers": []},
{"id": 4, "name": "Thursday", "workers": []},
{"id": 5, "name": "Friday", "workers": []},
]
And I managed to get two unique elements as following :
getTwoNonConsecutiveDays = () => {
var days = require('./weeks.json');
let selected = [];
let randomday = () => {
const shuffled = days.sort(() => 0.5 - Math.random());
// Get sub-array of first n elements after shuffled
selected = shuffled.slice(0, 2);
However when I am trying to put condition not to be consecutive is not working
if (selected[0].id === selected[1].id) {
console.log('greseala')}
selected[0].id
You may go, like that:
pick randomly centered window of 3 consequent items (2 if started on first array item) and get the one in the middle as the first item
remove selected triplet (e.g. using Array.prototype.splice()) to avoid picking first random item or its neighbors
pick randomly second random item from those that left
const src = [{"id":1,"name":"Monday","workers":[]},{"id":2,"name":"Tuesday","workers":[]},{"id":3,"name":"Wednesday","workers":[]},{"id":4,"name":"Thursday","workers":[]},{"id":5,"name":"Friday","workers":[]}],
getRandomPair = ([...a]) => {
const {random} = Math,
windowPos = 0|random()*(a.length-1),
e1 = a.splice(windowPos, windowPos ? 3 : 2)[windowPos && 1],
e2 = a[0|random()*a.length]
return [e1,e2]
}
console.log(getRandomPair(src))
.as-console-wrapper{min-height:100%;}
You can handle it that way:
var days = require('./weeks.json');
function randomIndex(array){
return Math.floor(Math.random() * array.length);
}
function randomPair(array){
var index1 = randomIndex(array), index2;
do{
index2 = randomIndex(array);
}while(Math.abs(index1 - index2) < 2);
return [array[index1], array[index2]];
}
console.log(randomPair(days));
Note: While loop and exit condition are inappropriate for small arrays, as #Yevgen Gorbunkov said in comment. An additional if condition can be added for checking the length of the array.
Update: While condition update based on #Andreas comment, use of Math.abs instead of multiple checks.
You can try following logic:
Create a loop with n as limit.
Create 2 variables:
List to hold items.
map to hold indexes that were generated.
Loop over and generate random index. To keep it in bounds, use Math.floor( Math.random() * array.length )
Now check in map if the neighboring elements exists. If yes, you can go to next value.
On finding unique index, add it to map and pust item to list.
function getNonConsecutiveDays(array, n) {
const list = [];
const map = {};
Array
.from({ length: n })
.forEach(() => {
let index = Math.floor( Math.random() * array.length );
if( map[ index + 1 ] || map[ index - 1]) {
index = (index + 1) % array.length;
}
let item = array[index];
map[ index ] = true;
list.push(item)
});
return list;
}
const data = [
{"id": 1, "name": "Monday", "workers": []},
{"id": 2, "name": "Tuesday", "workers": []},
{"id": 3, "name": "Wednesday", "workers": []},
{"id": 4, "name": "Thursday", "workers": []},
{"id": 5, "name": "Friday", "workers": []},
];
console.log( getNonConsecutiveDays(data, 2) )
Here's what I figured out:
Choose the index a of the first element from the range [0, arr.length).
If a == 0 or a == arr.length - 1, the first element has only 1 neighbour. Otherwise, it has 2 ones. So the number of possible choices of the second index is equal to arr.length - 1 - neighbours.
Choose the index b of the second element from the range [0, arr.length - 1 - neighbours)
If b >= a - 1, add 1 + neighbours to b.
And here's the code:
arr = ['A', 'B', 'C', 'D']
a = Math.floor(Math.random() * arr.length); // 1.
neighbours = 2; // 2.
if (a == 0 || a == arr.length - 1) neighbours = 1;
b = Math.floor(Math.random() * (arr.length - 1 - neighbours)); // 3.
if (b >= a - 1) b += neighbours + 1; // 4.
console.log(arr[a], arr[b]);
It should be guaranteed that the length of your array is greater than 3.

How to return the numerical position of a piece of data within a dataset?

Firstly sorry for how stupid I'm sure this question is.
I'm working through early examples in the 'Interactive Data Visualization for the Web' by Scott Murray.
One of the exercises is simply showing how d3 can create paragraphs of text based on datapoints within a dataset.
I'm trying to work out how to return the numerical position of a datapoint within a dataset to integrate within that paragraph.
For example, for the dataset [5, 10, 15], I'm looking for the result of three paragraphs reading:
The value of the datapoint 1 is 5.
The value of the datapoint 2 is 10.
The value of the datapoint 3 is 15.
How do I express the 1, 2, 3 in code?
var dataset = [ 5, 10, 15, 20, 25 ];
d3.select("body").selectAll("p")
.data(dataset)
.enter()
.append("p")
.text(function(d) { return "The value of datapoint " + **what would I write here to return 1, 2, 3 etc. depending on the position of each datapoint within the dataset** "is " + d; });
Try replacing function(d) with function(d, i) in the text function, the i is the index of the item. You can do this for .attr, .style, etc.
So your code would look like:
var dataset = [ 5, 10, 15, 20, 25 ];
d3.select("body").selectAll("p")
.data(dataset)
.enter()
.append("p")
.text(function(d, i) { return "The value of datapoint " + i + " is " + d; })

sum up object properties in array of objects into a single object Lodash

I have been trying to get this right and was having issues so figured i should ask ppl with more experience. I have an array of objects lets say called items, and I need to sum up some of the properties across different objects in the array and sum them up those at the end. The user can make a few selections and i need to only sum up the only the chosen properties in the array they give me so i thought maybe to use the _.pick function in lodash. If possible i would like to do that in one loop since the items array could have upto a 1000 items. Here is an example:
var items = [
{'lightBlue':4, 'darkBlue':2, 'red':4, 'orange':6, 'purple':7},
{'lightBlue':6, 'darkBlue':5, 'red':1, 'orange':2, 'purple':3},
{'lightBlue':2, 'darkBlue':4, 'red':3, 'orange':4, 'purple':9}
]
var userSelectedColors = ['lightBlue', 'darkBlue'];
What I want to see is all the blue's summed up like:
var summedUp = [{'lightBlue':12, 'darkBlue':11}];
Then sum up the results to get the total no
var totalCount = 23
Whats the best and performant way to get this in lodash. The array of userSelectedColors could be 1 or any combination of the colors.
Please provide an example, thanks your helps appreciated!
Use _.sumBy
var totalCount = _.sumBy(userSelectedColors, _.partial(_.sumBy, items));
var items = [
{ 'lightBlue': 4, 'darkBlue': 2, 'red': 4, 'orange': 6, 'purple': 7 },
{ 'lightBlue': 6, 'darkBlue': 5, 'red': 1, 'orange': 2, 'purple': 3 },
{ 'lightBlue': 2, 'darkBlue': 4, 'red': 3, 'orange': 4, 'purple': 9 }
], userSelectedColors = ['lightBlue', 'darkBlue'];
var totalCount = _.sumBy(userSelectedColors, _.partial(_.sumBy, items));
console.log(totalCount);
<script src="https://cdn.jsdelivr.net/lodash/4.13.1/lodash.min.js"></script>
Expanded, that looks like:
var totalCount = _.sumBy(userSelectedColors, function(prop) {
return _.sumBy(items, prop);
});
Without Lodash, a more performant solution would be something like this:
var totalCount = items.reduce(function(total, obj) {
return total + userSelectedColors.reduce(function(total, prop) {
return total + obj[prop];
}, 0);
}, 0);
var items = [
{ 'lightBlue': 4, 'darkBlue': 2, 'red': 4, 'orange': 6, 'purple': 7 },
{ 'lightBlue': 6, 'darkBlue': 5, 'red': 1, 'orange': 2, 'purple': 3 },
{ 'lightBlue': 2, 'darkBlue': 4, 'red': 3, 'orange': 4, 'purple': 9 }
], userSelectedColors = ['lightBlue', 'darkBlue'];
var totalCount = items.reduce(function(total, obj) {
return total + userSelectedColors.reduce(function(total, prop) {
return total + obj[prop];
}, 0);
}, 0);
console.log(totalCount);
<script src="https://cdn.jsdelivr.net/lodash/4.13.1/lodash.min.js"></script>
In terms of efficiency, I believe this is hard to beat, since it only iterates once through the array, but it's not quite as succinct as an approach like the one #4castle took. (Also, for only 1000 items, you're never going to notice the performance difference anyway.)
var items = [
{'lightBlue':4, 'darkBlue':2, 'red':4, 'orange':6, 'purple':7},
{'lightBlue':6, 'darkBlue':5, 'red':1, 'orange':2, 'purple':3},
{'lightBlue':2, 'darkBlue':4, 'red':3, 'orange':4, 'purple':9}
]
var userSelectedColors = ['lightBlue', 'darkBlue'];
var sums = {};
_.each(items, function (item) {
_.each(userSelectedColors, function (color) {
sums[color] = (sums[color] || 0) + item[color];
});
});
console.log('Summary: ', sums);
console.log('Grand total: ', _.sum(_.values(sums)));
Output:
Summary: { lightBlue: 12, darkBlue: 11 }
Grand total: 23
Getting the Summary
var summary = userSelectedColors.map(c => ({ [c]: _.sumBy(items, c)}))
Total
var total = _.sum(Object.values(summary))

Accessing custom coordinates from a layer with d3.layout.stack()

I have an array of data in the following form:
_data = [
{key: <String>, values: [<number>...]}
]
If the 'values' variable in each JSON object were an array of JSON coordinates (i.e. "x: ?, y: ?"), I could just use the following:
d3.layout.stack()
.values(function(d) {
return d.values;
});
Instead, I need to read x from each layer's values array as the index in the values array, and y as the number. Like so:
_data = [
{key: "oranges", values: [5, 6, 8]},
{key: "bananas", values: [2, 1, 9]}
]
becomes
"oranges": (0, 5), (1, 6), (2, 8);
"bananas": (0, 2), (1, 1), (2, 9);
How can this be done?
If I understand your question correctly, you are looking for the appropriate accessor function to access your specific data format from within D3's stack layout. The following code should do what you are looking for:
d3.layout.stack()
.values(function(d) {
return d.values.map(function(y, i) {
return { "x": i, "y": y };
});
});
The call to .map() will transform your data.values array and return an array consisting of objects having x and y coordinates set to the values you specified.
Is this what you're looking for?
var data = [
{key: "oranges", values: [5, 6, 8]},
{key: "bananas", values: [2, 1, 9]}
];
var newData = [];
var fruit;
var coords;
for (var i = 0; i < data.length; i++) {
coords = [];
for (var x = 0; x < data[i].values.length; x++) {
coords.push([x, data[i].values[x]]);
}
fruit = {};
fruit[data[i].key] = coords;
newData.push(fruit);
}
alert(JSON.stringify(newData));
Or something like this? Where you format only the array you need, when you need it?
d3.layout.stack()
.values(function(d) {
var coords = [];
for (var x = 0; x < d.values.length; x++) {
coords.push([x, d.values[x]]);
}
return coords;
});

Pushing a values in a Particular format to an array

I want to create an array dynamically which should be having a value in the format of
var dat1 = [
{ x: 0, y: 32.07 },
{ x: 1, y: 37.69 },
{ x: 2, y: 529.49 },
{ x: 3, y: 125.49 },
{ x: 4, y: 59.04 }
];
I want to store the whole thing in data into an array dynamically. I am getting these values from the json data. And I want an array to be in this format. How can I create it?
I tried this:
$.each(r_data, function(key, val) {
data1.push([{
x : i,
y : parseFloat(val.something)
}]);
i++;
});
...but didn't get the result I wanted.
Assuming you have
var data1 = [];
...and probably
var i = 0;
...prior to your code, your code will produce this structure:
var data1 = [
[ { x: 0, y: 32.07 } ],
[ { x: 1, y: 37.69 } ],
[ { x: 2, y: 529.49 } ],
[ { x: 3, y: 125.49 } ],
[ { x: 4, y: 59.04 } ]
];
Note how you've ended up with an array where each entry is another array, which in turn contains the object with the x and y properties.
I suspect you want:
var data1 = [];
var i = 0;
$.each(resultBar_data, function(key, value) {
data1.push({
x : i,
y : parseFloat(value.averagePrice)
});
i++;
});
...which just pushes the objects directly on data1, without wrapping them in extra arrays (note I've removed the [] around what's being pushed). You would access those entries like this:
console.log("The first entry is " + data1[0].x + "," + data1[0].y);
console.log("The second entry is " + data1[1].x + "," + data1[1].y);
format is an array of objects. In your following code, you are trying to push an array [{x:i, y:parseFloat(value.averagePrice)}] to the format array:
$.each(resultBar_data, function(key, value) {
format.push([{ /*array start*/
x : i,
y : parseFloat(value.averagePrice)
}] /*array end*/
);
i++;
});
Remember square brackets denote an array.
I think to fix your problem it should be:
/*i just removed the square brackets so that push now pushes an object,
not an array with a single object*/
format.push({
x : i,
y : parseFloat(value.averagePrice)
});
Hope this helps, please ask if you need more information or if I misunderstood your question!

Categories