D3 - Maintaining Sort on Data Change - d3.js

I'm ripping my hair out trying to figure this out.
I have a table being refreshed via WebSocket. It feeds an object with frequency counts. The key is a term. The value is a count. I take this and convert it to an array of arrays, where the top level array contains arrays of the format [key, value]. This works.
I sort and slice the array to get the Top 10. This works.
I feed this 2-dimensional array into d3.
d3.select('#table').select('tbody').selectAll('tr').data(dataArray, (element) => {
return element[0];
});
Now, first of all, I can't print this to peek at the update selection. Not on Chrome. Not on Firefox. No access to the selection data, just the _groups which does not include the exit() elements in the same place so there's no way to verify. But, fine. Whatevs.
I do this:
const enterRows = selection.enter().append('tr')
enterRows.append('td').classed('key', true).text((element) => { return element[0]; });
enterRows.append('td').classed('value', true);
enterRows.merge(selection).select('.value').text((element) => { return element[1]; })
selection.exit().remove();
At this point, the rows aren't ordered. I would expect them to be ordered. But they're not. Fine. Whatever.
selection.order();
Does nothing.
What do I do?
EDIT:
Here's a sample of the array as it goes into data():
[['B': 5], ['C': 3], ['A': 2]]
I can make no sense of the resulting table row order.
I just want to preserve the incoming array order.

enterRows.merge(selection).order() does the trick. No idea why it's necessary since ordering is supposed to be stable, but it does.

Related

How to test the text of the second element in cypress?

I can test the first element of the Dom element but I don't know how to get the second element?
it('should display highlight', () => {
const highlights = cy.get(`.${pageClass} .page_highlight`);
highlights.should('have.length', 2);
highlights.first().should('contain.text', translations.highlight);
});
There are two options for your case.
Since the total number of elements is just two in your case, you can use something called last. You can read more here.
If the number of elements is dynamic, you can use something called eq and pass the order of element as an index. You can read more here.

Problems with crossfilter filtering data correctly

I'm using crossfilter to feed the data in to dc.js. I'm not sure where the problem lay but I'm suspecting crossfilter. The issue I'm experiencing is that sometimes when you filter using on graph, it's rendering correctly on the second. But sometimes, depending on the selected group, each graph renders data that doesn't match the filter. Consider the attached screen shots
Here is the base state where the graphs are rendering all the data correctly
.
Here is where I'm filtering using the bottom graph. The same thing holds true when filtering from the top graph -- the data is showing, rendering, correctly
Now here is where things get funky. The first image is when I'm filtering using the bottom graph (note the additional rows are showing as active/highlighted) and the second image is when I'm filtering using the top graph. Depending on what items I select they are misrepresented, in different ways, on the other graph.
To me, it seems like it's an issue with crossfilter but I'm not certain. I added the onChange callback to crossfilter and I set a debugger; when the action is 'filtered' and it seems like the data is correct but it's not rendering correctly. So, I dunno.
Here is (a broken down version of) the code I'm using. It's part of a larger component but this is where I'm building the graphs. Please note, this is a truncated version of my dataset. SO won't let me include all of it.
dataset = [{"name":"Test 52718","quantity":"14"},{"name":"Test 52717","quantity":"20"},{"name":"Test 52716","quantity":"15"},{"name":"Test 52715","quantity":"46"},{"name":"Test 52714","quantity":"18"},{"name":"Test 52713","quantity":"29"},{"name":"Test 52712","quantity":"21"},{"name":"Test 52711","quantity":"27"},{"name":"Test 52709","quantity":"10"},{"name":"Test 52710","quantity":"40"},{"name":"Test 52708","quantity":"13"},{"name":"Test 52707","quantity":"22"},{"name":"Test 52706","quantity":"15"},{"name":"Test 52705","quantity":"13"},{"name":"Test 52704","quantity":"14"},{"name":"Test 52703","quantity":"40"},{"name":"Test 52702","quantity":"41"},{"name":"Test 52701","quantity":"32"},{"name":"Test 52700","quantity":"11"},{"name":"Test 52699","quantity":"8"},{"name":"Test 52698","quantity":"24"},{"name":"Test 52697","quantity":"25"},{"name":"Test 52696","quantity":"24"},{"name":"Test 52695","quantity":"49"},{"name":"Test 52694","quantity":"17"},{"name":"Test 52693","quantity":"15"},{"name":"Test 52692","quantity":"28"},{"name":"Test 52691","quantity":"29"},{"name":"Test 52690","quantity":"47"},{"name":"Test 52689","quantity":"22"},{"name":"Test 52688","quantity":"38"},{"name":"Test 52687","quantity":"39"},{"name":"Test 52686","quantity":"35"},{"name":"Test 52685","quantity":"29"},{"name":"Test 52684","quantity":"5"},{"name":"Test 52683","quantity":"17"},{"name":"Test 52682","quantity":"46"},{"name":"Test 52681","quantity":"23"},{"name":"Test 52680","quantity":"5"},{"name":"Test 52679","quantity":"14"},{"name":"Test 52678","quantity":"37"},{"name":"Test 52677","quantity":"46"},{"name":"Test 52676","quantity":"37"},{"name":"Test 52675","quantity":"48"},{"name":"Test 52674","quantity":"13"},{"name":"Test 52673","quantity":"13"},{"name":"Test 52672","quantity":"42"},{"name":"Test 52671","quantity":"14"},{"name":"Test 52670","quantity":"37"},{"name":"Test 52669","quantity":"30"},{"name":"Test 52668","quantity":"28"},{"name":"Test 52667","quantity":"40"},{"name":"Test 52666","quantity":"12"},{"name":"Test 52665","quantity":"8"},{"name":"Test 52664","quantity":"38"},{"name":"Test 52663","quantity":"47"},{"name":"Test 52662","quantity":"27"},{"name":"Test 52661","quantity":"18"},{"name":"Test 52660","quantity":"25"},{"name":"Test 52658","quantity":"46"},{"name":"Test 52659","quantity":"46"},{"name":"Test 52657","quantity":"26"},{"name":"Test 52656","quantity":"46"},{"name":"Test 52655","quantity":"20"},{"name":"Test 52654","quantity":"16"},{"name":"Test 52653","quantity":"50"},{"name":"Test 52652","quantity":"12"},{"name":"Test 52651","quantity":"43"},{"name":"Test 52650","quantity":"22"},{"name":"Test 52649","quantity":"32"},{"name":"Test 52648","quantity":"5"},{"name":"Test 52647","quantity":"27"},{"name":"Test 52646","quantity":"14"},{"name":"Test 52645","quantity":"15"},{"name":"Test 52644","quantity":"13"},{"name":"Test 52642","quantity":"37"},{"name":"Test 52641","quantity":"27"},{"name":"Test 52640","quantity":"31"},{"name":"Test 52639","quantity":"22"},{"name":"Test 52638","quantity":"48"},{"name":"Test 52637","quantity":"49"},{"name":"Test 52636","quantity":"[ EMPTY VALUE ]"},{"name":"Test 52635","quantity":"[ EMPTY VALUE ]"},{"name":"Test 52634","quantity":"[ EMPTY VALUE ]"},{"name":"Test 52633","quantity":"[ EMPTY VALUE ]"},{"name":"Test 52632","quantity":"[ EMPTY VALUE ]"},{"name":"Test 52631","quantity":"[ EMPTY VALUE ]"},{"name":"Test 52630","quantity":"[ EMPTY VALUE ]"},{"name":"Test 52629","quantity":"[ EMPTY VALUE ]"},{"name":"Test 52628","quantity":"[ EMPTY VALUE ]"},{"name":"Test 52627","quantity":"[ EMPTY VALUE ]"},{"name":"Test 52626","quantity":"[ EMPTY VALUE ]"},{"name":"Test 52625","quantity":"[ EMPTY VALUE ]"},{"name":"Test 52624","quantity":"[ EMPTY VALUE ]"},{"name":"Test 52623","quantity":"[ EMPTY VALUE ]"},{"name":"Test 52622","quantity":"[ EMPTY VALUE ]"},{"name":"Test 52621","quantity":"[ EMPTY VALUE ]"},{"name":"Test 52620","quantity":"[ EMPTY VALUE ]"},{"name":"Test 52619","quantity":"[ EMPTY VALUE ]"},{"name":"Test 52618","quantity":"[ EMPTY VALUE ]"}]
And then the main body of my code
graphCrossfilter = crossfilter(dataset);
dimension = graphCrossfilter.dimension(function (d) {return d.quantity});
group = dimension.group().reduceCount();
rowChart = dc.rowChart("#rowChart" , 'DataGraphs')
.height(700)
.width(500)
.x(d3.scale.linear().domain(d3.extent(aData, function (d) {return d.quantity})))
.transitionDuration(0)
.elasticX(true)
.dimension(oDimension)
.group(oGroup);
barChart = dc.barChart("#barChart", 'DataGraphs')
.height(400)
.width(400)
.outerPadding(0.05)
.barPadding(0.05)
.renderHorizontalGridLines(true)
.renderVerticalGridLines(true)
.elasticX(true)
.elasticY(true)
.brushOn(true)
.transitionDuration(0)
.x(d3.scale.ordinal())
.xAxisLabel('Text Field')
.xUnits(dc.units.ordinal)
.yAxisLabel('Count')
.dimension(dimension)
.group(group);
graphCrossfilter.onChange(function(changeAction) {
if (changeAction === "filtered") {
debugger;
}
});
If anyone can offer me ideas and/or where I can look to try to figure out what's going on, I'd very much appreciate it.
thnx,
Christoph
The values returned by Crossfilter dimension accessors must be naturally ordered. It looks to me like your dimension is returning a non-numeric value for certain records where the data isn’t populated. When this happens you will often see filtering issues like what you describe. Try testing for missing data in your dimension accessor and returning 0 in that case.

Lodash sortedby object list is stuck in _wrapper_

I am experimenting with lodash sorting. I got the lodash to sort the object list although the sorted results are stuck in wrapper. If I use .value(), then I get unsorted keys output.
var testData = {c:1,b:2,a:3}
var sortedKeys = _(testData).keys().sortBy(key => {return testData.key});
console.log(sortedKeys);
will produce:
LodashWrapper {__wrapped__: {…}, __actions__: Array(2), __chain__: false, __index__: 0, __values__: undefined}
__actions__:(2) [{…}, {…}]
__chain__:false
__index__:0
__values__:undefined
__wrapped__:
a:3
b:2
c:1
__proto__:Object
__proto__:lodash
What is it that I am missing in order to get sorted object list out of lodash wrapper.
When you do testData.key, I'm pretty confident that you actually mean to be doing testData[key].
That alone allows the method to work properly i.e. return an array of Object keys sorted by values. Note that you still have to call .value() if you'd like to unwrap the lodash object.
If there's something else you're expecting, please clarify.
const testData = {c:1,b:2,a:0}
const sortedKeys = _(testData).keys().sortBy(key => {return testData[key]})
/* can do without the return like the below as well */
// const sortedKeys = _(testData).keys().sortBy(key => testData[key])
console.log(sortedKeys.value())
// produces ['a','c','b']
If you want the key and value pair, try the below.
_(obj).toPairs().sortBy(0).value()
There are few things that are happening here which I think are important to note:
First you are starting your sorting statement with the short notation for the lodash _.chain method which allows the results of one operation to flow into the next one. This is similar to how _.flow works in lodash/fp.
Chaining requires the last operation in the chain to end with values() in order to get your actual result from the lodash wrapper. So if you did:
_(testData).keys().sortBy(key => {return testData.key}).values(); // OR
_.chian(testData).keys().sortBy(key => {return testData.key}).values();
You would get some results.
Second issue is that in your flow you get the keys of the objects but then you are not actually sorting by them. To do this you need something like this:
var testData = {c:1, b:2, a:3}
console.log(_.chain(testData).keys().sortBy().value());
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
The difference here is in the sort function where you already have the keys as `[ 'c', 'b', 'a' ] so you just need to sort them. They are not objects anymore but simply strings.
Hope this helps.

How to do a couchbase view query when the startkey and endkey are the same?

Map Fuction
function(doc, meta) {
if (doc.login_timestamp) {
emit(dateToArray(doc.login_timestamp), doc.username);
}
}
Produces:
[2011,10,10,10,10,09] “scalabl3”
[2012,12,24,17,03,59] “scalabl3”
[2013,01,01,08,22,23] “scalabl3”
[2013,01,25,10,38,01] “tgrall”
[2013,01,25,11,02,32] “jzablocki”
[2013,02,01,11,02,32] “scalabl3”
How do I get just the records from 2013?
I tried
startkey=[2013]&endkey=[2013]
That doesn't work because by definition results must be greater than equal the start key AND less than the end key
What is the correct way to do this?
Since you have an array, you can specify arrays for start and end. So one of two ways that should work:
startkey=[2013]&endkey=[2014]
or
startkey=[2013]&endkey=[2013,99]
In the second example, you give a value greater than any you expect to see in a valid entry.

Access variable hash depth values with square brackets notation

Given this hash:
hash1= { node1: { node2: { node3: { node4: { node5: 1 } } } } }
We access inside nodes with square brackets like this:
hash1[:node1][:node2][:node3][:node4]
Now I have a hash that I know will always be nested as it is an XML response from a SOAP webservice, but neither the depth of the hash nor the names of the nodes stay the same. So it would be nice if I could ask the user of my application for the hash depth and store it in a variable. And then be able to do hash1[:hash_depth] and achieve the same result as above.
I have accomplished what I want by the following code:
str = 'node1,node2,node3,node4'
str_a = str.split(',')
hash_copy = hash1
str_a.each { |s| hash_copy = hash_copy.[](s.to_sym) }
hash_copy
=> {:node5=>1}
hash1[:node1][:node2][:node3][:node4]
=> {:node5=>1}
that is asking the user to enter the hash depth separated by commas, store it in a string, split it, make an array, clone the original hash, go down each level and modify the hash till I get to the desired node. Is there a way to do it with the square brackets notation and using a variable to store the depth without modifying the hash or needing to clone it?
Edit:
someone answered with the following (can't see his post anymore???)
hash_depth="[:node1][:node2][:node3][:node4]"
eval "hash1#{hash_depth}"
Although eval does everything you need, there is another approach, since you already have the working code for comma-separated list:
hash_depth="[:node1][:node2][:node3][:node4]"
csh = hash_depth.gsub(/\A\[:|\]\[:|\]\Z/, { '][:' => ',' })
#⇒ "node1,node2,node3,node4"
And now you are free to apply your existing function to csh.
If this is a webapp, I think you should prepare a list of short textareas, which starts with a single text item, and the user can keep adding a new item to the list by clicking on a button. The areas will be filled by the user, and will be sent.
Then, you will probably receive this through some serialized form. You decode this to get an array of strings:
str_a = ["node1", "node2", "node3", "node4"]
and you can reach the inner element by doing:
str_a.inject(hash1){|h, s| h[s.to_sym]} #=> {:node5 => 1}

Resources