D3 V4 Tree Search and Highlight - javascript

So, I really love this example from Jake Zieve shown here: https://bl.ocks.org/jjzieve/a743242f46321491a950
Basically, on search for a term, the path to that node is highlighted. I would like to accomplish something similar but with the following caveats:
I would like to stay in D3 v4.
I'm concerned about cases where the path doesn't clear out on next node pick OR what happens when there are two nodes of the same
name (I would ideally like to highlight all paths)
I would like to AVOID using JQuery
Given a set search term (assume you're already getting the string from somewhere) I know I need to make use of the following lines specifically (you can see my stream of consciousness in the comments) but I'm just not quite sure where to start.
// Returns array of link objects between nodes.
var links1 = root.descendants().slice(1); //slice to get rid of company.
console.log(links1); //okay, this one is nice because it gives a depth number, this describes the actual link info, including the value, which I am setting link width on.
var links2 = root.links(); // to get objects with source and target properties. From here, I can pull in the parent name from a selected target, then iterate again back up until I get to source. Problem: what if I have TWO of the same named nodes???
console.log(links2);
Thoughts on this? I'll keep trying on my own, but I keep hitting roadblocks. My code can be found here: https://jsfiddle.net/KateJean/7o3suadx/
[UPDATE]
I was able to add a filter to the links2 to call back a specific entry. See
For example:
var searchTerm = "UX Designer"
var match = links2.filter(el => el.target.data.name === searchTerm); //full entry
console.log(match);
This single entry gives me all associated info, including the full list of all points back up to "COMPANY"
So, I can GET the data. I think the best way to accomplish what I want is to somehow add a class to each of these elements and then style on that "active" class.
Thank you!

Related

Why my D3.js Dropdown filter is random ?

I've build the following table here :
https://bl.ocks.org/simonbreton/d4d2ea338d1bacc6ce3d0a295529bcb4
However as you can see if you try to select different option, data doesn't update correctly.
Some doesn't show up (here in the picture batman for example) and others doesn't remove. For exemple in the picture again, I've expected to remove superman, captain and Antman for seeing only batman data.
What's wrong with my code ?
thanks a lot !
By default d3 identifies data by index in the array. It doesn't play well with filtering - some elements get dropped and it changes the indexes of succeeding elements. Adding a key function to your data binding could help, however, there is also a problem with your dataset. It contains duplicates and there is no property that could distinguish them. If names were unique you could use a key function like below:
var rows = tbody.selectAll("tr")
.data(filterdata, function (d) { return d.name})

Parse nodes in a dijit.tree

I'm struggling with a dijit.Tree and I can't find what I need in the dojo documentation...
I want to change the style of a few elements in my tree, according to some conditions.
I am able to identify the elements through a combination of for loops and if evaluations :
itemList = this.tree.model.store._arrayOfAllItems;
for (var index in itemList) {
item = itemList[index];
if (item.<property> == ...) {
...
//This is where I want to change the style
...
}
...
}
But then, I fail to get the node id to call dojo.addClass(nodeId, newClass).
Am I parsing through the proper list, with the model.store._arrayOfAllItems? Is there a way to parse through the node list instead, and still access the data properties?
Thank you very much for your help!
Edit on 2015-11-23
With Richard's comments, I was able to obtain the result I was looking for. I have added a handler that connects the tree's onOpen event to a method that gets the open node map (from tree._itemNodesMap) and then fetch through the store. For every item in the store, it adjust the css if the id of the item being validated has an associated node in the open node map. It then looks recursively for children.
Thanks Richard for your help!
If you have the id of the node inside the tree, you can use the getNodesByItem function that tree has.
Although if your tree is dynamic and the contents can change, I would suggest writing a function that not only adds to your store but also adds to a class for the node formed in the tree.

Optimized Algorithm to compare Templates of two URLs

EDITED, Please Read Again, As I added some work of mine
My task is to compare templates of two URLS. I am ready with my algorithm. But it takes too much time to give final answer.
I wrote my code in Java using Jsoup and Selenium
Here, Templates means the way any page presents its contents.
Example:-
Any shopping website have page of any Shoes, that contains,
Images in the left.
Price and Size in the right.
Reviews in the bottom.
If two URLS are of any specific product , then it return "Both are from same templates". Example , this link and this link have same template.
If one URL shows any product and another URL shows any category ,then it shows "No match".
Example, this link and this link are from different template.
I think that this algorithm requires some optimization, that's why I am posting this question in this forum.
My algorithm
Fetch, parse two input URLS and make their DOM trees.
Then if any page contains , UL and TABLE , then remove that tag. I done this because, may be two pages contains different number of items.
Then, I count number of tags in both URLS. say, initial_tag1, initial_tag2.
Then, I start removing tags that have same position on corresponding pages and same Id and their below subtree, if that tree has number of nodes less than 10.
Then, I start removing tags that have same position on coresponding pages and same Class name and their below subtree, if that tree has number of nodes less than 10..
Then, I start removing tags that have no Id ,and No Class name and their below subtree, if that tree has number of nodes less than 10.
Steps 4, 5, 6 have (N*N) complexity. Here, N, is number of tags. [In this way, in every step DOM tree going to shrink]
When it comes out from this recursion, then I check final_tag1 and final_tag2.
If final_tag1 and final_tag2 is less than initial_tag1*(0.2) and initial_tag2*(0.2) then I can say that Two URL matched, otherwise not.
I think a lot about this algorithm, and I found that removing node from DOM tree is pretty slow process. This may be the culprit for slowing this algorithm.
I discussed from some of geeks, and
they said that use a score for every tag instead of removing them, and add them , and > at the end return (score I Got)/(accumulatedPoints) or something similar, and on the
basis of that you decide two URLS are either similar or not.
But I didn't understand this. So can you explain this saying of some geek, or can you give any other optimized algorithm, that solve this problem efficiently.
Thanks in advance. Looking for your kind response.
For comparing webpages there basically two ways, the fast and the slow one :
Compare URLS : fast
Compare DOM : slow (and complicated)
In your case, it appears that the first two items match a similar regular expression and the categories match another regexp.
Here is a short JAVA solution
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TestRegexp {
public static void main(String[] args) {
String URL_ITEM_1 = "http://www.jabong.com/Puma-Flash-Ind-Black-Running-Shoes-187831.html";
String URL_ITEM_2 = "http://www.jabong.com/Lara-Karen-Full-Sleeve-Black-Polyester-Top-With-Cotton-Lace-196636.html";
String URL_CATEGORY_1 = "http://www.jabong.com/kids/shoes/floaters/";
String URL_CATEGORY_2 = "http://www.jabong.com/women/clothing/womens-tops/";
Pattern itemPattern = Pattern.compile("http://www\\.jabong.com/([\\w\\p{Punct}\\d]+)\\.html");
Pattern categoryPattern = Pattern.compile("http://www\\.jabong.com/([\\w\\p{Punct}]+/)+");
System.out.println("Matching items");
Matcher matcher = itemPattern.matcher(URL_ITEM_1);
System.out.println(matcher.matches());
matcher = itemPattern.matcher(URL_ITEM_2);
System.out.println(matcher.matches());
matcher = itemPattern.matcher(URL_CATEGORY_1);
System.out.println(matcher.matches());
matcher = itemPattern.matcher(URL_CATEGORY_2);
System.out.println(matcher.matches());
System.out.println("Matching categories");
Matcher category = categoryPattern.matcher(URL_ITEM_1);
System.out.println(category.matches());
category = categoryPattern.matcher(URL_ITEM_2);
System.out.println(category.matches());
category = categoryPattern.matcher(URL_CATEGORY_1);
System.out.println(category.matches());
category = categoryPattern.matcher(URL_CATEGORY_2);
System.out.println(category.matches());
}
}
And the output :
Matching items
true
true
false
false
Matching categories
false
false
true
true
It validates the first two first URLS as being items, the two last as being categories.
I hope it matches your requirement. Feel free to adapt in JS.
To improve complexity of your algorithm, supposing you are using Jsoup, you must adapt your data structure to your algorithm.
4) What do you mean by position of tag ? the Xpath of the tag ?
If yes, precompute this value once for each tag O(n) and store this value in each node. If required you can also store it in a HashMap to retrieve in O(1).
5) Index you tag by class name using MultiMap. You will save lot of computation
6) Index class with no Id, no class name
All these pre computations can be performed in one traversal of the tree so O(n).
Generally if you want to reduce computation, you will have to store more data in the memory. Since DOM page are very small data, this is no problem in your case.

Retrieve Data Bindings d3

I'm attempting to create a d3 plugin ala this stackoverflow question:
How to make a d3 plugin?
But within his shown example
(function() {
d3.selection.prototype.editable = d3.selection.enter.prototype.editable = function() {
return this.attr('data-editable', true);
};
})();
I don't see how he can actually retrieve the data associated with the selection. Is this something that can even be retrieved with this extension of d3.selection? I mucked through the d3 source a little bit but found myself far more confused than before.
Can someone who has written a d3 extension/plugin guide me in the right direction?
In javascript, the object that this refers to is (usually, and in your example code above) determined by the object which calls the function where this appears.
Hence the line return this.attr('data-editable', true); will return the exact same d3 selection object that calls editable.
So you will get back the normal old d3 selection object, just as you would in the ordinary d3 method chaining pattern. Once you have that, getting the data is just a matter of looking up the API for the d3 selection object.
If you are interested specifically in how to get the data back, take a look at the data method. From the link above, when that method is called with no arguments:
If values is not specified, then this method returns the array of data
for the first group in the selection. The length of the returned array
will match the length of the first group, and the index of each datum
in the returned array will match the corresponding index in the
selection. If some of the elements in the selection are null, or if
they have no associated data, then the corresponding element in the
array will be undefined.

jQuery "Autocomplete" plugin is messing up the order of my data

I'm using Jorn Zaefferer's Autocomplete plugin on a couple of different pages. In both instances, the order of displayed strings is a little bit messed up.
Example 1: array of strings: basically they are in alphabetical order except for General Knowledge which has been pushed to the top:
General Knowledge,Art and Design,Business Studies,Citizenship,Design and Technology,English,Geography,History,ICT,Mathematics,MFL French,MFL German,MFL Spanish,Music,Physical Education,PSHE,Religious Education,Science,Something Else
Displayed strings:
General Knowledge,Geography,Art and Design,Business Studies,Citizenship,Design and Technology,English,History,ICT,Mathematics,MFL French,MFL German,MFL Spanish,Music,Physical Education,PSHE,Religious Education,Science,Something Else
Note that Geography has been pushed to be the second item, after General Knowledge. The rest are all fine.
Example 2: array of strings: as above but with Cross-curricular instead of General Knowledge.
Cross-curricular,Art and Design,Business Studies,Citizenship,Design and Technology,English,Geography,History,ICT,Mathematics,MFL French,MFL German,MFL Spanish,Music,Physical Education,PSHE,Religious Education,Science,Something Else
Displayed strings:
Cross-curricular,Citizenship,Art and Design,Business Studies,Design and Technology,English,Geography,History,ICT,Mathematics,MFL French,MFL German,MFL Spanish,Music,Physical Education,PSHE,Religious Education,Science,Something Else
Here, Citizenship has been pushed to the number 2 position.
I've experimented a little, and it seems like there's a bug saying "put things that start with the same letter as the first item after the first item and leave the rest alone". Kind of mystifying. I've tried a bit of debugging by triggering alerts inside the autocomplete plugin code but everywhere i can see, it's using the correct order. it seems to be just when its rendered out that it goes wrong.
Any ideas anyone?
max
EDIT - reply to Clint
Thanks for pointing me at the relevant bit of code btw. To make diagnosis simpler i changed the array of values to ["carrot", "apple", "cherry"], which autocomplete re-orders to ["carrot", "cherry", "apple"].
Here's the array that it generates for stMatchSets:
stMatchSets = ({'':[#1={value:"carrot", data:["carrot"], result:"carrot"}, #3={value:"apple", data:["apple"], result:"apple"}, #2={value:"cherry", data:["cherry"], result:"cherry"}], c:[#1#, #2#], a:[#3#]})
So, it's collecting the first letters together into a map, which makes sense as a first-pass matching strategy. What i'd like it to do though, is to use the given array of values, rather than the map, when it comes to populating the displayed list. I can't quite get my head around what's going on with the cache inside the guts of the code (i'm not very experienced with javascript).
SOLVED - i fixed this by hacking the javascript in the plugin.
On line 549 (or 565) we return a variable csub which is an object holding the matching data. Before it's returned, I reorder this so that the order matches the original array of value we were given, ie that we used to build the index in the first place, which i had put into another variable:
csub = csub.sort(function(a,b){ return originalData.indexOf(a.value) > originalData.indexOf(b.value); })
hacky but it works. Personally i think that this behaviour (possibly coded more cleanly) should be the default behaviour of the plugin: ie, the order of results should match the original passed array of possible values. That way the user can sort their array alphabetically if they want (which is trivial) to get the results in alphabetical order, or they can preserve their own 'custom' order.
What I did instead of your solution was to add
if (!q && data[q]){return data[q];}
just above
var csub = [];
found in line ~535.
What this does, if I understood correctly, is to fetch the cached data for when the input is empty, specified in line ~472: stMatchSets[""] = []. Assuming that the cached data for when the input is empty are the first data you provided to begin with, then its all good.
I'm not sure about this autocomplete plugin in particular, but are you sure it's not just trying to give you the best match possible? My autocomplete plugin does some heuristics and does reordering of that nature.
Which brings me to my other answer: there are a million jQuery autocomplete plugins out there. If this one doesn't satisfy you, I'm sure there is another that will.
edit:
In fact, I'm completely certain that's what it's doing. Take a look around line 474:
// loop through the array and create a lookup structure
for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
/* some code */
var firstChar = value.charAt(0).toLowerCase();
// if no lookup array for this character exists, look it up now
if( !stMatchSets[firstChar] )
and so on. So, it's a feature.

Categories