I'm building a website using React that shows a tree structure. (In terms of HTML it's not hierarchical at all, but just a bunch of divs at various positions, with SVG to draw connectors.)
To lay out the tree, I use an algorithm (Buchheim et al's improvement on Walker's algorithm) which looks at the widths of all the tree nodes and calculates the appropriate positions for each node.
I'm having trouble integrating this the "right" way. I cannot know the node's width until I'm inside its componentDidMount function, but at that time I don't know the position yet. So I need to change the position later, and modifying the DOM afterwards doesn't seem the React way. Making sure the tree component can access the nodes also feels slightly hackier than it perhaps needs to be.
Here's what I do now:
The tree component's render function generates an array of node components.
I've added some additional dictionaries directly to the tree component to store data I need. I found no way to use props or state for this. (I copied this from the Masonry mixin.)
The node component's componentDidMount function calls a callback on the tree component to register its DOM node there. This feels pretty awful but it works.
The tree component's componentDidMount function lays out the tree, sets the positions of the DOM nodes, and draws the connections between the nodes using SVG in a div that is not managed by React. That also feels a bit dirty but it's more because I needed to quickly get something that worked.
The main thing I dislike is having to sneak in parallel data structures to be able to change the positions of the DOM nodes after the nodes' componentDidMount function has been called.
Is there a cleaner way to do this?
Related
I have a list of components (~50-100), these components are quite advanced; they contain a lot of child elements that obviously takes some effort to be drawn out.
The result is that the UI "freezes" 1-2 seconds when navigating to the page, it actually freezes before getting to the page.
The UI is also a bit laggy when all the components are loaded.
I'm thinking it should be possible to load them in chunks in some way? Or only draw the full component out if it's visible on the screen somehow, and have a skeleton component when it's not in the viewport.
What is the recommended approach here?
Thanks in advance.
You must avoid useless renders with memo.
Be careful not to call APIs for no reason.
Also you should use useCallback and useMemo to avoid call methods which return same value for children components.
Context API is a good option to avoid Drilling states as props to nested components.
You could use lazy loading to render children components along with scroll as well.
The problem I am facing is that rendering a lot of web-components is slow. Scripting takes around 1,5s and then another 3s for rendering (mostly Layout + Recalculate styles) for ~5k elements, I plan to put much more than that into the DOM. My script to prepare those Elements takes around 100-200ms, the rest comes from constructor and other callbacks.
For normal HTML Elements a perf gain can be achieved with documentFragment, where you basically prepare a batch of elements, and only when you're done you attach them to the DOM.
Unfortunately, each web-component will call its constructor and other callbacks like connectedCallback, attributeChangedCallback etc. When having a lot of such components it's not really optimal.
Is there a way to "prerender" web-components before inserting them into the DOM?
I've tried to put them inside template elements and clone the contents, but the constructor is still called for each instance of my-component. One thing that did improve the performance is putting content that is attached to the shadow DOM inside a template outside of component and cloning it instead of using this.attachShadow({ mode: 'open' }).innerHTML=..., but that's not enough.
Do you really need to render all ~5k elements at once? You will face performance problems rendering that many elements in the DOM independently of if you manage to pre-initialize the components in memory.
A common technique for this scenario is to use a technique called "windowing" or "lazy rendering": the idea is to render only a small subset of your components at any given time depending on what's on the user viewport.
After a very quick search, I didn't find a library for web-components that implements this, but for reference in React you have react-window and in Vue vue-windowing
I'm playing around with different ways of integrating D3 and Angular, in the context of learning both of these frameworks, and am hoping for some input:
My client application receives a JSON array of nodes and an array of edges from my server.
The central component of the client is a graph visualization implemented as a D3 force-directed layout: For each node in the nodes array, a (SVG) circle element is added to this visualization, and lines between these circles are added for each edge in the edges array.
Of course, D3's selection.data( ) makes it trivial to add these elements and bind each to the data it represents, but the graph visualization is only part of a much larger application: I need to create different types of elements representing these same nodes and edges in different parts of the application (which D3 has nothing to do with), and I'd like to keep all of these elements bound to a single dataset.
My primary goal is minimizing code complexity, and - although I've fallen in love with the simplicity and elegance of D3's data-binding functionality - I've arrived at the tentative conclusion that using it in one part of the application while using Angular to do the same in other parts is creating unnecessary complexity, and that the simplest approach would be to use Angular to handle the data-binding/element-creation
In other words, instead of using selection.data( ).enter( ).append( ) to create SVG elements, I'm thinking I should do so using a ng-repeat="node in nodes", perhaps creating each as a custom directive to allow for custom functionality. Having done so, I would then need to get these elements "into" D3, i.e. managed by its force-directed layout.
Is my reasoning here sound? In particular, I'm worried that I'm overlooking complications this will create with regard to object constancy, which is an important requirement as nodes will be entering, exiting and moving about the screen constantly and I need these transitions to be smooth. How would you recommend I go about integrating my angular-created elements into D3 (more precisely, getting them into my force.nodes{ } and force.links( ) arrays) to avoid any such complications?
Finally, I'm also considering a strategy that I'm hoping might give me the best of both worlds: I could use D3 to create my SVG elements and bind them to their respective datum, but rather than executing that code in the link function of visualization directive (as I've been doing, and as all the Angular/D3 tutorials I've found do), I could run it in the compile function, and do something like this:
d3.select('SVG')
.selectAll('.node')
.data('nodeDataArray')
.enter( )
.append('circle')
.attr("class", "node-icon"); //...other attributes/styles etc
where node-icon is the normalized name of a directive with a restrict property that includes C. If this runs in the compile method, angular should then compile all of these directives as normal, adding any additional functionality / interfaces with other directives (etc.), the same way it does with any other type of element. Right?
This is the option I'm most attracted to intuitively - are there any pitfalls of it I might be overlooking, which might make the former strategy preferable?
I have been pondering pretty much the same problem for a while and come to the following conclusion.
The simplest way to integrate Angular created elements with d3 is to add the directives with .attr and then .call the compile service on the d3 generated elements. Like this:
mySvg.selectAll("circle")
.data(scope.nodes)
.enter()
.append("circle")
.attr("tooltip-append-to-body", true)
.attr("tooltip", function(d){
return d.name;
})
.call(function(){
$compile(this[0].parentNode)(scope);
});
Here is a Plunker.
I think the idea of generating elements with Angular ngRepeat rather than d3 is working against the frameworks. D3 does not expect to be handed a bunch of elements. It wants to be handed data - almost always an array. It then has a stack of excellent functions to convert that data into various SVG or HTML elements. Let it do that.
It seems from this quote...
D3 makes it trivial to add elements and bind each to the data it represents, but the graph visualization is only part of a much larger application: I need to create different types of elements representing these same nodes and edges in different parts of the application (which D3 has nothing to do with), and I'd like to keep all of these elements bound to a single dataset.
... you are implying that generating elements with d3 somehow prevents you from binding the same data to different parts of the application. I can't see why. Just have d3 generate elements from a scope array (as is done in the linked Plunker). Then use the same dataset wherever you want in the usual Angular way. Other parts of the application can update the dataset and a $watch callback can re-render the d3 graphic.
I'm trying to build a force-directed layout, wherein the connected nodes have their own internal layout that is not simply a recursive force-directed layout (which, I believe, would be possible with the hierarchy layout). The "inner" layout would be custom, but, for illustration let's say I wanted the nodes, internally, to have a partition layout. Is this possible?
My question was really twofold:
can you pull off having more than one style of layout (for instance, a bubble graph inside a force-directed graph) in a sensible way with D3, or is D3 the wrong tool for such a thing, and
Can you use D3 layouts for each of these layouts, or do you have to do everything custom.
In the end, the design changed, and no longer called for this odd scenario. Being much more familiar with D3, though, I think I can answer.
Yes. It can be done. Each layout is its own discrete object, with its own data on which to work, and can be given its own DOM elements to populate. Creating two layouts that even shared the same data and DOM outputs would probably work, if you could manage the interaction between the two (making sure one only overrode changes from the other when desired).
What I know you can do for sure is manually manipulate anything that D3 is doing. At one point during development, actually, I did have two layouts on the same page, come to think of it. I was laying out half the graph with pre-determined x/y coordinates, and allowing the rest to be laid out by the force directed layout. That first set, the manually placed nodes, could have been placed by some other logic than pre-determined coordinates, and the functionality would have been roughly the same.
Can an ASP.NET TreeView, when you click the + sign be made to expand upwards instead of downwards? So that "Children" nodes appear above their "Parents."
Essentially I want to logically be taking what would normally be a singular leaf node, making it the root of my tree, and making what is logically it's Parent into a Child Node on my Tree, but have that Child Node still display above the Parent when the Parent gets expanded.
Think a company OrgChart, but starting with the employee (leaf node) and going up.
I can't seem to find anything specifying direction on MSDN. I would like to avoid having to re-write how the Javascript of the TreeView works, but would be open to it if no other way is possible.
No, you cannot do that with the TreeView component.
Attempting to do that would involve inheriting from the TreeView and writing your custom Render function.
There is a simpler approach to it though. You could write out all your data to a json object, and use one of the numerous javascript data visualization libraries to present it.
I really like the javascript InfoVis toolkit. You can make very detailed graphs using it.