Part 3: Making D3 Trees SharePointy in SPFx Web Parts

My previous post in this series, on how to integrate D3 with SPFx, was a bit of a monster. I covered off how to create a D3 tree in a SharePoint client web part built using SPFx. It was a steep learning curve and whilst the end result was quite pleasing it did look like all the D3 tree demos that are out there, using blue circles for tree nodes, as it does.

This post is going to be much lighter and I’m just going to cover off one thing and that’s how to swap out our circle node and replace them with graphics coming from SharePoint. Having said that, to understand what’s going on here you probably need to do the hard yards with my previous 2 posts in this series.

What I am trying to do here is build a new kind of UI onto a SharePoint list or library which uses a D3 tree to allow users to glide through folders and views, instead of continual clicking all the time resulting in post backs. I’m still using fake data for now but we’ll address that in due course. At the moment, I want to make the UI look, well a little bit more SharePointy.

Images vs Fonts

One thing that immediately occurs to me is that we could use better iconographic to represent the nodes in the tree. Now, there are 2 ways I could have gone here, I could either:

  • Add svg images element to the group elements that represent nodes in the tree.
  • Add a standard HTML element (typically an <i/> tag is used) and set the class attribute to an icon font library like Office Fabric or Fonts Awesome.

Using font libraries is the modern what of doing things and as we are drawing our tree in an SVG element, if we use a scalable fonts rather than static images then the graphic should remain sharp and crisp no matter the level of zoom.

However, using a font library turns out to be a bit of a problem in an SVG as we need to apply a class attribute to a standard HTML element. It is possible to add HTML elements to an SVG using a foreignobject element but we have a problem with this approach because foreign objects aren’t supported in Internet Explorer. They work just fine in Edge and Chrome but IE is still alive and kicking and I don’t really want to impose this level of restriction on my users at this time (maybe it will be fine in a couple of years when IE is truly dead).

So, its off to plan B and this involves adding an SVG image element to the group element for each tree node and setting the image URL to something appropriate.

Sourcing Images

This may mean that we have to source the graphics we need and bundle them up in the web part. If you want to use custom icons in your tree then you can most certainly do that of course. Or you could publish your icons graphics to a CDN and then just set the image URLs to the absolute URL at the CDN location.

However, SharePoint is awash with icons that you can absolutely reuse. If you have access to an on premises deployment of SharePoint, take a look in the Image folder on a SharePoint server.

But are these images still there in SharePoint Online? Of course they are, because SharePoint uses them.

However, to get names of the files you want to link to will mean that you will need to have access to an on-prem deployment of SharePoint, so you can dig into the Image folder as shown above.

As we know that the web part will be hosted in SharePoint, all we have to do is provide the SVG image elements with site relative URLs to our graphics of choice and we should be away!

Swapping Circles for Icons

First up, we need some constants to hold URLs to useful graphics and that’s what I’ve done here:

public static readonly IMAGE_URL_FOLDER_CLOSED : string = "/_layouts/15/images/folder.gif";

public static readonly IMAGE_URL_FOLDER_OPEN : string = "/_layouts/15/images/openfold.gif";

public static readonly IMAGE_URL_GENERIC_DOCUMENT : string = "/_layouts/15/images/doc_sp16.gif";

For the purposes of this demonstration, I shall be working with just 3 graphics:

  • An open folder, used to indicate that a parent node is open and expanded.
  • A closed folder, used to indicate a parent node that is closed and collapsed.
  • A generic document to be used for leaf nodes (all those without any children).

In due course we’ll be able to suck up an icon depending on the file type (if we are working with documents that is) but for now, as we are still working with fake data, we’ll just use some simple business logic to set the node with the appropriate icon.

If you’ve read part 2 of this series then you’ll know that we appended the circles as part of the tree node in the nodeEnter method that I defined in utility support class. So, all we have to do is to comment out the code that appends the circle and replace it with code that appends an image and then set the URL accordingly. The snippet below shows what I’ve commented out and what I’ve replaced it with in the nodeEnter method.

//Append a circle

// nodeEnter.append('circle')
//         .attr("class", `${styles.node}`)                 
//         .attr('r', 0)
//         .style("fill", function(d: any) {
// return d._children ? "lightsteelblue" : "#fff";
//         });

nodeEnter.append("svg:image")
    .attr('x', -8)
    .attr('y', -8)
    .attr('width', 16 )
    .attr('height', 16)
    .attr("xlink:href", function(d){
                            return D3Utilities.NodeImageUrl(d);
                        });

What I have done here is just set the size of the graphic to be 16x16px and position it relative to the top-left of the node.

I then set the href of the image.

We’ve also got to make similar changes to the updateNode as shown below:

// nodeUpdate.select('circle')
//                 .attr('r', 5)                      
//                 .style("fill", function(d) {
//                     return d._children ? "lightsteelblue" : "white";
//                 });

nodeUpdate.select("image")
            .transition()
            .duration(Constants.DURATION_NODE_TRANSITIONS / 2)
            .attr("xlink:href", function(d){
                return D3Utilities.NodeImageUrl(d);
            });

But here we just need to set the image URL. It can be seen that the URL is provided by the value returned by a new function I created in the D3Utilities class called NodeImageUrl, as shown below:

//Returns a URL to an image for the node
public static NodeImageUrl(node : any): string{       

    if (D3Utilities.isLeafNode(node))
        return Constants.IMAGE_URL_GENERIC_DOCUMENT;

    else if (node.children)
        return Constants.IMAGE_URL_FOLDER_OPEN;                   

    else
        return Constants.IMAGE_URL_FOLDER_CLOSED;

}

The logic here is really quite straight forward.

If the node is a leaf node then set the URL of the image to be the generic document.

Else if we are dealing with a node that has children, so we must set the node to use the one of the folder icon URLs.

If you followed me in my last blog post, you will now that each node that has child nodes will have those child nodes stored in either a children or a _children array property depending on whether the node is expanded or collapsed respectively. Remember the act of clicking on a node simply toggles the child nodes between these 2 arrays.

So, a leaf node has neither a children nor a _children array, an expanded node has child items in its children array but the _children array is empty and collapsed nodes store their child nodes in _children array and the children array is empty. This means that all we have to do is check to see if the children array is not null or empty in which case, we return the URL to the open folder icon, otherwise the node is currently collapsed and so the closed folder icon should be used.

Also, remember that we’ve got to dispose of any nodes no longer needed when a node is collapsed and so we have to update the nodeExit method as well.

// On exit reduce the node circles size to 0
// nodeExit.select('circle')
//             .attr('r', 0);

nodeExit.select('image')
    .style('fill-opacity', 0);

Previously I transitioned the circle into non-existence by shrinking their radius down to zero.

Obviously, images don’t have a radius property so instead we just fade them out by reducing their opacity.

The Reveal

And that’s all there is to it!

It’s beginning to look less like a D3 demo and a bit more like the SharePoint UI we are after.

Just bear in mind that you won’t see any images if you gulp serve to the local workbench which is perfectly understandable as we are most likely not developing on a SharePoint server. To see these icons, you’ll have to access the workbench of a live SharePoint Online site.

In Conclusion

In this post we addressed how to replace the circle nodes in our D3 tree with graphic icons sucked up from SharePoint.

We considered using a font library such as Office Fabric or Fonts Awesome as this would give us nice vector scalable images but instead, we opted to use graphic image files because the font library approach is not supported in Internet Explorer and we decided that that making a solution that does not support IE was a constraint we didn’t want to impose on our users at this time.

We could have used any image files and maybe saved them to a CDN but instead we decided to use the SharePoint set of stock images and linked to them using constants providing server relative URLs to the graphics we wanted to use. This means the UI will start to have a decidedly SharePoint look and feel to it and we don’t have to bother source custom images. Just remember that SharePoint hosted icons would be accessible from the local workbench.

In the previous post we used colour coding in the circles to indicate the expanded or collapsed state of nodes but here we swapped out the circle for the images and used similar business logic to return either a closed or open folder image depending on whether the node was collapsed or expanded, respectively.

Next Up

In my previous post, I designed the web part to support different links line types that connect nodes in the tree but we don’t currently have a way to let the user choose their preferred link type. That’s what we’ll cover next.

2 comments

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: