D3 Key Function - function

As readers might gather from the following...I am fairly new to D3....I am experimenting at the moment using .enter() and .exit().remove(). I am trying to understand what the key function does...? I am viewing the DOM with Chrome > Console and cannot see any obvious differences between .data(dataSet, keyFunction) and without the key function .data(dataSet).
Can anybody please suggest any experiments to try (or console expressions) that may help me understand exactly what the mysterious key function does....

I'm also new to d3 and was struggling with the key function. I didn't find Tristan Reid's answer illuminating, because it doesn't really talk much about the key function.
Let's work through an example, first without a key function, and then with.
Here's our initial html before applying javascript. We've got two divs, and there is no data attached to anything.
<body>
<div>** First div **</div>
<div>** Second div **</div>
</body>
Calling data() with no key function
Let's add a couple lines of javascript.
var arr1 = [35, 70, 24, 86, 59];
d3.select("body")
.selectAll("div")
.data(arr1)
.enter()
.append("div")
.html(function(d) { return d });
What does our html look like now? Here is the html along with the values of the associated data (comments added).
<body>
<div>** First div ** </div> <!-- data: 35 -->
<div>** Second div ** </div> <!-- data: 70 -->
<div>24</div> <!-- data: 24 -->
<div>86</div> <!-- data: 86 -->
<div>59</div> <!-- data: 59 -->
</body>
The data() call matched an array of divs with an array of values by use of a key. The default keys used for the arrays is the indexes. So these are the keys that were used.
selected divs (by text) key data elements key
----------------------- --- ------------- ---
** First div ** 0 35 0
** Second div ** 1 70 1
24 2
86 3
59 4
Going by the keys, two of the data elements have matches in the selected divs -- those with keys 0 and 1. Those matching divs get bound to data, but nothing else happens them.
All the data elements without a matching key get passed to enter(). In this case, there is no match for the keys 2, 3, and 4. So those data elements get passed to enter(), and a new div is appended for each of them. The appended divs are also bound to their respective data values.
Calling data() with a key function
Let's change our javascript, keeping what we have but adding a couple more lines. We'll perform the same selects with a data call (with a different array), but this time using a key function. Notice the partial overlap between arr1 and arr2.
var arr1 = [35, 70, 24, 86, 59];
d3.select("body")
.selectAll("div")
.data(arr1) // no key function
.enter()
.append("div")
.html(function(d) { return d });
var arr2 = [35, 7, 24, 2];
d3.select("body")
.selectAll("div")
.data(arr2, function(d) { return d }) // key function used
.enter()
.append("div")
.html(function(d) { return "new: " + d});
The resulting html looks like this (with comment added):
<body>
<div>** First div** </div> <!-- data: 35 -->
<div>** Second div ** </div> <!-- data: 70 -->
<div>24</div> <!-- data: 24 -->
<div>86</div> <!-- data: 86 -->
<div>59</div> <!-- data: 59 -->
<div>new: 7</div> <!-- data: 7 -->
<div>new: 2</div> <!-- data: 2 -->
</body>
The second call to data() used the value returned by the function for the keys. For the selected elements, the function returns a value derived from the data that had already been bound to them by the first call to data(). That is, their key is based on their bound data.
For the second data() call, the keys used for matching look like this.
selected divs (by text) key data elements key
----------------------- --- ------------- ---
** First div ** 35 35 35
** Second div ** 70 7 7
24 24 24 24
86 86 2 2
59 59
The data elements without matching keys are 7 and 2. Those data elements are passed to enter(). So we get two new divs appended to the body.
Okay, so now let's look back at the original post. The OP said that there was no difference between the data() call with a function and without. That's probably because -- as Tristan Reid suggests -- the key function was being used on html elements that had no bound data. When there's no bound data, there will be no matching keys, so all of data elements will get passed to the enter() function.
Working through this example helped illuminate for me the relationships between selections, keys, and bound data. Hopefully it will be helpful to someone else.

The key function explains how to join the data to the elements. The default key function if you don't supply one is to use the index.
To understand all this, consider that a d3 selectAll/data is performing two phases of matching. The first is the selector, e.g. d3.selectAll('div'), which will match all divs. The second is the data join, data([1,2,3]), which is looking for elements with data properties which match the data you pass in. The emphasis is because I think understanding this is fundamental to getting full benefit from d3.
Here's an example (also a fiddle) that demonstrates the difference.
function id (d) { return d; }
d3.select('.no-key').selectAll('div')
.data([1,2,3])
.enter().append('div')
.text(id);
d3.select('.yes-key').selectAll('div')
.data([1,2,3], id)
.enter().append('div')
.text(id);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div class='no-key'>
<div class='a'>already here</div>
</div>
<br/>
<div class='yes-key'>
<div>already here</div>
</div>
I applaud the efforts of the other answer, but this answer doesn't require parsing a console out, it shows the actual difference in functionality.
Why does this difference happen? Here are the gory details:
If you do a d3.selectAll('div') you are selecting all divs. If you then do a .data([1,2,3]), you are joining that data to those divs: but the join doesn't have a key function, so it isn't looking to see if the divs have [1,2,3] as data elements, it's just going to use the first 3 divs that it finds.
If you instead do .data([1,2,3], function(d){return d;}), your key function says to match [1,2,3] against the data in the divs, so unless you have existing divs that have data elements, you won't match any existing divs.
The illustration of all this is in the .enter().append('div'), which of course adds any necessary divs that weren't found in the above matches. That's the bottom line of all this enter().append business: It adds (number of data elements) - (number of existing elements that match the key function)
Hope this helps!

Per request from my SO hero, let me try to address #markthekoala 's desire for a console experimentation that clearly demonstrates the effect of specifying a key function within the .data() operator.
http://bl.ocks.org/migurski/4601038
I actually consider this an even more pointed example than the excellent one I linked to in my comments above! NOTE: the long title of the block has a good explanation on what is happening under the hood, so read it carefully.
You can play with the block's code in this FIDDLE.
Honestly, I find it very hard to improve on the explanations in these two examples, and particularly on the experimentation so cleverly carried out on the second one.

Related

Passing a list as the parameter of a template

I have created a Template in my MediaWiki which I want to pass "free text" to. To keep it simple, imagine it as a table in which the first named parameter goes into cell 1 and the other named parameter goes into cell 2.
Writing the Template itself seemed easy enough, but I keep stumbling into errors when I try to pass certain structures in the arguments. My first issue was with a code block: if I wrote
{{MyTemplate|first=
// code...
|other=
// other code instead...
}}
then the wiki thought that the pipe character was part of the preformatted text block, and in the end I ended up without a second parameter. This issue was solved using <pre></pre>, but I thought it was more of a workaround than a solution.
Now I'm in trouble again when I try to pass a list:
{{MyTemplate|first=
* Item 1a
* Item 2a
|other=
* Item 1b
* Item 2b
}}
Again, I end up with the first item (Item 1a) ignored (not rendered as a list item), the second rendered ok (Item 2a, as a list item), then a double pipe, an asterisk and "Item 1b", then the list goes on with "Item 2b".
I am clearly doing something wrong, but I have no idea what to do, nor I could find any useful example in the docs. Please help me understand how to correctly pass any text (which may include lists or other markdown constructs) as a template argument.
EDIT
Here is the code of my Template:
{|
!First !!Other
|-
|{{{first|}}} ||{{{other|}}}
|}
Also, I'm sorry, but I typed the usage wrong: the four more spaces were a leftover from the snippet above. I've fixed that.
#Ivanhercaz I've tried your suggestion (which only differs from mine for the empty lines), and still got the same result.
In this case I have to suppose how is the template you created, because you didn't shared the structure, just how you call and two parameters. Because of this reason I am going to suppose that you create a template as:
{{{first|}}}
{{{other|}}}
About your first question, you can use {{!}} to escape the pipe character (|). From the version 1.24 this is a magic word, but before the solution to this issue was to create a template with a pipe and then replace the | by {{!}}. The use of the magic word is the same, but you is unnecessary to create the template.
About your second question, taking in account that you have a similar structure to what I wrote at the top of this answer, if I wrote a page with this:
{{MyTemplate|first=
* Item 1a
* Item 2a
|other=
* Item 1b
* Item 2b
}}
I get a two lists and two code blocks: list one with "Item 1a" and the first code block with * Item 2a; list two with "Item 2a" and the second code block with * Item 2b. It doesn't seem to what you get rendered:
the first item (Item 1a) ignored (not rendered as a list item), the second rendered ok (Item 2a, as a list item), then a double pipe, an asterisk and "Item 1b", then the list goes on with "Item 2b". This happened due to the extra space before the * in the parameters.
So I recommend to you to share the code of the template to check and answer to you better. Of course, if what you want is to pass two different lists in each parameter you could make something like:
{{Test|first=
* Item 1a
* Item 2a
|other=
* Item 1b
* Item 2b
}}
This structure solved the problem I mentioned. But I wait your comment to be sure and edit my answer if it is necessary.
Edit
Now, with the code of the template I understand your problem. To work correctly and without problems with templates, tables and lists you have to consider that the * need to have an line break after the | and ||. What I recommend to you is insert this line breaks
{|
! Wikitext !! Rendering
|-
|
{{{first|}}}
||
{{{other|}}}
|}
I think it is an easy and clean way to do what you are trying. So, if you use the template in this way:
{{Test
|first=* Item 1a
* Item 2a
|other=* Item 1b
* Item 2b
}}
Or in this other way:
{{Test
|first=
* Item 1a
* Item 2a
|other=
* Item 1b
* Item 2b
}}
You will have a table like that (I add a class=wikitable to have a more beautiful table to show):
I hope this trick has been helpful.

Scroll to table row or list item in an Angular app

In the code snapshot below in app.component.html, I display clickable rows in a table in Lines 39 to 58. Once the user clicks a row, I display other relevant rows in a table named suraTable as shown in Lines 63 to 77. Namely, when the user clicks on a row, the function onSelectAya(aya, suraTable) in app.component.ts is called. That function loads the necessary rows and the table is displayed.
What I am stuck with is this. Suppose suraTable ends up with 100 rows. The current behaviour is that rows 1, 2, 3... are displayed of course. I need to be able to show the table at Row 50 say (information carried in the parameter aya on Line 91). How do I scroll to that position? I tried the scrollTop way but when checking the values before and after the setting of suraTable.scrollTop, the value is always 0 (0 pixels that is). Line 100 shows that when console.log(suraTable) is reached, expected output is generated in the browser, which means that I am getting hold of the element correctly to start with.
So, how do I scroll to a specific row in a table, or to an item in a list (I could convert the whole structure to a list if easier) from within the app.component.ts component? Thank you.
<mat-row id="{{task.taskId}}"
*matRowDef='let task; columns: columnsReadyTask;let i = index;'></mat-row>
if working with datasources, you will need some delay after getting element on the page with id...
setTimeout(() => {
const selectedRow = document.getElementById(taskId);
if(selectedRow) {
selectedRow.scrollIntoView({block: 'center'});
}
}, 500);
This may not work on old browsers.
If this is simply a scrolling thing, you could add specific id to your rows so you can know theyr number, and then do
var elmnt = document.getElementById("rowNumber"+rowNumber);
elmnt.scrollIntoView();
If when the scrolling is taking part your element is not rendered on DOM, try adding a callback for when that rendering is finished. If that is not possible, maybe a setTimeout can do.

Printing a table with sage math

The assignment is to construct a two-column table that starts at x= -4 and ends with x= 5 with one unit increments between consecutive x values. It should have column headings ‘x’ and ‘f(x)’. I can't find anything helpful on html.table(), which is what we're supposed to use.
This what I have so far. I just have no idea what to put into the html.table function.
x = var('x')
f(x) = (5 * x^2) - (9 * x) + 4
html.table()
You might want to have a look at sage's reference documentation page on html.table
It contains the following valuable information :
table(x, header=False)
Print a nested list as a HTML table. Strings of html will be parsed for math inside dollar and double-dollar signs. 2D graphics will be displayed in the cells. Expressions will be latexed.
INPUT:
x – a list of lists (i.e., a list of table rows)
header – a row of headers. If True, then the first row of the table is taken to be the header.
There is also an example for sin (instead of f) with x in 0..3 instead of -4..5, that you can probably adapt pretty easily :
html.table([(x,sin(x)) for x in [0..3]], header = ["$x$", "$\sin(x)$"])
#Cimbali has a great answer. For completeness, I'll point out that you should be able to get this information with
html.table?
or, in fact,
table?
since I would say we want to advocate the more general table function, which has a lot of good potential for you.

Simulink Matlab function block deleting rows from a vector

That i want to do is to delete certain rows (or columns doesn't really mater...) from a given vector.
By going through Simulink's components found out that there is nothing performing such an operation,there are blocks help one add elements but nothing clearly for removing,so ended up trying to delete them by using a function block and following the online examples that demonstrate the usage of "[]".Lets say that i want to delete the second column of the vector u,i do u(:, 2) = [];.
That works absolutely fine in a separate m file or function but unfortunately not in a function block returning:
"Simulink does not have enough information to determine output sizes for
this block. If you think the errors below are inaccurate, try specifying
types for the block inputs and/or sizes for the block outputs."
and:
Size mismatch (size [4 x 4] ~= size [4 x 3]).
The size to the left is the size of the left-hand side of the assignment.
Function 'MATLAB Function' (#107.41.42), line 4, column 1:
"u"
Launch diagnostic report.
Is there any alternative you can suggest to remove several elements in a given vector in Simulink?
Thanks in advance
George
Finally,managed to do it without function block.There is a much easier way,by using Pad,and defining the output vector to be shorter than the input resulting in truncation.

more minimaler cubism.js horizon chart from json example

Following up on a previous question... I've got my minimal horizon chart example much more minimaler than before ( minimal cubism.js horizon chart example (TypeError: callback is not a function) )
<body>
<div class="mag"></div>
<script type="text/javascript">
var myContext = cubism.context();
var myMetr = myContext.metric(function(start, stop, step, callback) {
d3.json("../json/600s.json.php?t0=" + start/1000 + "&t1=" + stop/1000 + "&ss=" + step/1000, function(er, dt) {
if (!dt) return callback(new Error("unable to load data, or has NaNs"));
callback(null, dt.val);
});
});
var myHoriz = myContext.horizon()
.metric(myMetr);
d3.select(".mag")
.call(myHoriz);
</script>
</body>
The d3.json() bit calls a server side .php that I've written that returns a .json version of my measurements. The .php takes the start, stop, step (which cubism's context.metric() uses) as the t0, t1, and ss items in its http query string and sends back a .json file. The divides by 1000 are because I made my .php expect parameters in s, not ms. And the dt.val is because the actual array of my measurements is in the "val" member of the json output, e.g.
{
"other":"unused members...",
"n":5,
"val":[
22292.078125,
22292.03515625,
22292.005859375,
22292.02734375,
22292.021484375
]
}
The problem is, now that I've got it pared down to (I think) the bare minimum, AND I actually understand all of it instead of just pasting from other examples and hoping for the best (in which scenario, most things I try to change just break things instead of improving them), I need to start adding parameters and functions back to make it visually more useful.
Two problems first of all are, this measurement hovers all day around 22,300, and only varies +/- 10 maybe all day, so the graph is just a solid green rectangle, AND the label just says constantly "22k".
I've fixed the label with .format(d3.format(".3f")) (versus the default .2s which uses SI metric prefixes, thus the "22k" above).
What I can't figure out is how to use either axis, scale, extent, or what, so that this only shows a range of numbers that are relevant to the viewer. I don't actually care about the positive-green and negative-blue and darkening colours aspects of the horizon chart. I just used it as proof-of-concept to get the constantly-shifting window of measurements from my .json data source, but the part I really need to keep is the serverDelay, step, size, and such features of cubism.js that intelligently grab the initial window of data, and incrementally grab more via the .json requests.
So how do I keep the cubism bits I need, but usefully change my all-22300s graph to show the important +/- 10 units?
update re Scott Cameron's suggestion of horizon.extent([22315, 22320])... yes I had tried that and it had zero effect. Other things I've changed so far from "minimal" above...
var myHoriz = myContext.horizon()
.metric(myMetr)
.format(d3.format(".2f"))
.height(100)
.title("base1 (m): ")
.colors(["#08519c", "#006d2c"])
// .extent([22315, 22320]) // no effect with or without this line
;
I was able to improve the graph by using metric.subtract inserting it above the myHoriz line like so: (but it made the numerical label useless now):
var myMetr2 = myMetr.subtract(22315);
var myHoriz = myContext.horizon()
.metric(myMetr2)
.format...(continue as above)
All the examples seem so concise and expressive and work fine verbatim but so many of the tweaks I try to make to them seem to backfire, I'm not sure why that is. And similarly when I refer to the API wiki... maybe 4 out of 5 things I use from the API work immediately, but then I always seem to hit one that seems to have no effect, or breaks the chart completely. I'm not sure I've wrapped my head around how so many of the parameters being passed around are actually functions, for one thing.
Next hurdles after this scale/extent question, will be getting the horizontal time axis back (after having chopped it out to make things more minimal and easier to understand), and switching this from an area-looking graph to more of a line graph.
Anyway, all direction and suggestion appreciated.
Here's the one with the better vertical scale, but now the numerical label isn't what I want:
Have you tried horizon.extent? It lets you specify the [min, max] value for the horizon chart. By default, a linear scale will be created to map values within the extent to the pixels within the chart's height (specified with `horizon.height or default to 30 pixels).