Why is calling .select() changing my bound data? - d3.js

In my mental model of d3, calling .select() should not change what data is bound to selections.
I've been encountering a case where calling .select() and passing it a function changes the bound data and I would appreciate an explanation of what's wrong with my mental model.
Here's a minimal case:
// Setup
let body = d3.selectAll('body')
let exampleData =
[
{_id:"groupOne", items:['one','two']},
{_id:"groupTwo", items:['three','four']}
];
// Add some divs
body.selectAll('div').data(exampleData).enter().append('div');
// Add some p children of the divs
body.selectAll('div').selectAll('p').data((d)=>d.items).enter().append('p').text((d)=>d);
// Issue
console.log(body.selectAll('div').data()); // data is the same as exampleData
body.selectAll('p').select(function(){return this.parentNode}); // Select parents of all p
console.log(body.selectAll('div').data()); // Data is now ["two","four"]
and here is a live version on bl.ocks.org .

From the documentation of d3: https://github.com/d3/d3/wiki/Selections#select
If the current element has associated data, this data is inherited by the returned subselection, and automatically bound to the newly selected elements.
So you select the p elements with body.selectAll('p') which has data ["one,"two","three","four"] (this data is inherited by the returned subselection, and automatically bound to the newly selected elements.)
Then you make a subselection with .select(function(){return this.parentNode});
The subselect will 'iterate' 4 times.
Which will be the div with:
<div><p>one</p><p>two</p></div> for the first two
and <div><p>three</p><p>four</p></div> for the last two.
For the first iteration; parentNode <div><p>one</p><p>two</p></div>,
will get(inherit) data "one".
The second iteration: data "two".
In the 3rd iteration; parentNode <div><p>three</p><p>four</p></div>,
will get data "three".
And four the 4th iteration: data "four".
However, I think what you're trying to do is something like this:
body.selectAll('p').select(this.parentNode); // Select parents of all p
console.log(body.selectAll('div').data()); // Data is now the same as exampleData
(Without the return statement)

Related

Vuetify v-data-table change a row color for a few seconds

We've just moved over from bootstrap to Vuetify, but i'm struggling with something.
We have some updates sent (over signalR) that update a list of jobs, i'd like to be able to target a job that has been changed and change the row color for that particular job for a few seconds so the operator can see its changed.
Has anyone any pointers on how we can do this on a Vuetify v-data-table
Thanks
I ran into the same problem. This solution is a bit crude and a bit too late, but may help someone else.
In this example I change the colour of the row permanently until the page reloads. The problem with a temporary highlight is that if the table is sorted there is no way to put the row in the visible part of the table - v-data-table will put it where it belongs in the sort, even if it's out of the view.
Collect the list of IDs on initial load.
Store the list inside data of the component.
Use a dynamic :class attribute to highlight rows if the ID is not in the list (added or edited rows)
Solution in detail
1. Use TR in the items template to add a conditional class.
<template slot="items" slot-scope="props">
<tr :class="newRecordClass(props.item.email, 'success')">
<td class="text-xs-center" >{{ props.item.email }}</td>
:class="newRecordClass(props.item.email, 'success')" will call custom method newRecordClass with the email as an ID of the row.
2. Add an additional array to store IDs in your data to store
data: {
hydrated: false,
originalEmails: [], <--- ID = email in my case
3. Populate the list of IDs on initial data load
update(data) {
data.hydrated = true; // data loaded flag
let dataCombined = Object.assign(this.data, data); // copy response data into the instance
if (dataCombined.originalEmails.length == 0 ) {
// collect all emails on the first load
dataCombined.originalEmails = dataCombined.listDeviceUsers.items.map( item => item.email)
}
return dataCombined;
}
Now the instance data.originalEmails has the list of IDs loaded initially. Any new additions won't be there.
4. Add a method to check if the ID is in the list
newRecordClass(email, cssClass) {
// Returns a class name for rows that were added after the initial load of the table
if (email == "" || this.data.originalEmails.length==0) return "" // initial loading of the table - no data yet
if (this.data.originalEmails.indexOf(email) < 0 ) return cssClass
}
:class="newRecordClass(..." binds class attribute on TR to newRecordClass method and is being called every time the table is updated. A better way of doing the check would be via a computed property (https://v2.vuejs.org/v2/guide/computed.html#Computed-Properties). Vue would only call it when the underlying data changed - a method is called every time regardless.
Removing the highlight
You can modify newRecordClass method to update the list of IDs with new IDs after a delay to change the colour to normal.
#bakersoft - Did you find a solution? I suspect there is an easier way to skin this cat.

Add row name to cde table component

I have a table component in my Pentaho CDE dashboard and data source is sql. I want to make a table like this
enter image description here
I need to add a column as row names for this table and a row on the top of column header.
I found a method here:
Table Component SubColumns
to add header, but how can i add a column before other columns?
I think a clever way to do the extra column part would be by modifying the query result object after it's retrieved from the query, so that you add the columns and rows as needed. You can do this in the PostFetch callback function, which takes the query result object as first argument:
function yourTableComponentPostFetch(queryResult) {
// Let's say you have an array of row names
var rowNames = ['Title1', 'Title2', ... ];
// Add a "title" column for every row
queryResult.resultset.forEach(function (row, idx) {
// Push it at the beginning of the row array
row.unshift(rowNames[idx]);
});
// Change metadata description of columns to reflect this new structure
queryResult.metadata.unshift({
colName: 'Title',
colIndex: -1, // this makes sense when reindexing columns below ;)
colType: 'String'
});
// One last re-indexing of metadata column descriptions
queryResult.metadata.forEach(function (column, idx) {
// The title added column will be 0, and the rest will rearrange
column.colIndex++;
});
}
The only tricky part is the part about modifying the metadata since you are effectively changing the structure of the dataset, and making sure the queryResult object is updated in-place instead of just changing its reference (queryResult = myNewQueryResult would not work), but the code I proposed should do the trick.

kendo UI grid dataitem set method

grid.dataItem(selectedRow)
this is return the selected row which is a kendo.data.ObservableObject.
this object has all the columns for that grid's selected row. Is there a way to iterate thru all the columns and update.
or do i have to do it like this:
dataitem.set("Id", 1);
dataitem.set("name", Eric);
dataitem.set("age", 12);
As far as I understand what you are trying is to copy one JavaScript object into a Grid item, correct?
Let's assume that you have the new value in val:
var val = {
Id : 1,
name: "Eric",
age: 12
};
And you want to copy it in the selected row.
There are several ways of doing it:
What you just did.
Iterate through the different keys of val and copy the value.
Use jQuery extend.
Option 2.
for (var key in val) {
if (val.hasOwnProperty(key)) {
dataitem.set(key, val[key]);
}
}
Option 3.
$.extend(item, val);
item.set("uid", kendo.guid());
The first instruction performs a deep copy of val into item.
The second instruction makes the item dirty by just changing the UID.
NOTE: You don't need to update every single field using set, is enough changing one and all will get updated.

jqGrid - After re-ordering columns, sorting maps data to original columns

EDIT: Final solution is below.
Whether I try to implement column re-ordering via header dragging or via the column chooser plugin, after re-ordering the columns, clicking on any column header to sort results in the sorted columns being loaded into their original positions in the table. Using the sortable method:
sortable: {
update: function (perm) {
/*
* code to save the new colmodel goes here
*/
// the following line doesn't seem to do anything... just seems to return an array identical to 'perm'
$("#mainGrid").jqGrid("getGridParam", "remapColumns");
// if included, the next line causes the headers to not move
$("#mainGrid").jqGrid("remapColumns", perm, true);
// this alternate allows them to move, but the newly sorted columns still get remapped to their original position
$("#mainGrid").jqGrid("remapColumns", [0,1,2,3,4,5,6,7,8,9,10,11,12], true);
/* the following allows the headers to move, and allows the sort to occur ONLY
* if the order coming back from the database is unchanged. Note that in my real
* code I create an array of consecutive integers to pass as the first param to
* remapColumns()
*/
$("#mainGrid").jqGrid("remapColumns", [0,1,2,3,4,5,6,7,8,9,10,11,12], true, false);
}
}
When the page is reached for the first time, it creates a default column model from an xml file. When the user re-orders the headers, the new column model and column names are stored in the database as JSON strings. When the user makes another database call, the function reads the new column order from the database and creates the data array with the new ordering.
The problem seems to be that after jqGrid has remapped the columns, it still expects to see the data coming back from the server in the original order. So if the original data was
[ [A1, B1, C1], [A2, B2, C2], [A3, B3, C3] ]
after remapping the columns to the order C | A | B, jqGrid still wants the data to came back in the original order.
My final solution was to remove the code that saves the column model state from the sortable.update() function and to put it into window.onbeforeunload(). This way, the state is only saved when the user exits the page.
Hope this helps someone else.
See edited question. Without a method to update the colModel, the best solution seems to put the state save function into window.onbeforeunload().

jqgrid randId() produces duplicates after page reload

On my grid, after a user enters text on the bottom row, I am adding another row so they can fill out another row if needed. The grid will grow as needed by the user. This is working fine, however after a page reload and populating from db, the addrowdata() function does not honor existing row ids and creates duplicates, starting from 1 again, e.g. jqg1. It should look at existing row ids and create new unique ids. So if I have 5 rows already, it might start at jqg6. Here is the relevant code inside onCellSelect:
var records = jQuery("#table-1").jqGrid('getGridParam', 'records');
var lastRowId = jQuery("#table-1").jqGrid('getDataIDs')[records - 1];
if (lastRowId == id)
{
jQuery('#table-1').addRowData(undefined, {}, 'last');
}
I have also tried $.jgrid.randId() instead of undefined, same results as expected.
Thanks
Ryan
I think that the error is in the part where you fill grid with the data from the database. The data saved in the database has unique ids. The ids are not in the form jqg1, jqg2, ... So if should be no conflicts. You should just fill the id fields of the JSON with the ids from the database.
One more possibility is that you just specify the rowid parameter (the first parameter) of addRowData yourself. In the case you will have full control on the new ids of the rows added in the grid.
The code of $.jgrid.randId function is very easy. There are $.jgrid.uidPref initialized as 'jqg' and $.jgrid.guid initialized to 1. The $.jgrid.randId function do the following
$.jgrid.randId = function (prefix) {
return (prefix? prefix: $.jgrid.uidPref) + ($.jgrid.guid++);
}
If it is really required you can increase (but not decrease) the $.jgrid.guid value without any negative side effects.

Resources