Back to This Blog's Index

Adding a Tag cloud to the blog site

A post in C# and ASP.Net Core
May 22 2021 | 7 min to read | 0 comments

I wanted to add a list of tags to the homepage so that a user could quickly see topics covered on the site as a whole, but also navigate to posts related to those topics.  I realized that a word cloud would also be rather stylish.  So this is a journal of how I (hopefully by the time I finish typing this article) got it to work.  

Part 1: Finding the Tech

I knew there likely wasn't much hope for a C# library to display this, but thinking back to when I learned through FreeCodeCamp (which is an excellent learning resource), I remembered the D3.js library, which is used for data visualizations.  I shortly found a stack overflow article, that helped me get some of the basics down.  Now that the groundwork seems to be laid, I'll crack my knuckles and get it working with my C# code!


Part 2: Sharpening up

For a bit of background, my homepage uses a viewModel, to not only get a list of all the blogs, but also the latest post on the site as a feature.  I'm now adding a property for tags, so that we can easily bring them over to the JS.  The stack overflow Javascript doesn't compute how often we've used each word, so I'll do that behind the scenes, normalizing everything to lowercase.

On second thought, I don't want to pass the tags to the javascript, I'm instead going to use 2 lists, one of strings called TagNames, and another of ints called TagCounts.  later in the JS we can access these by index in a foreach loop.  

The only parameter that gets passed to the tagIndex (which we will link to) is the string TagName, so theres really no need to send along the whole object with Ids and everything else.  So I'm running the following foreach loop to accomplish this: 

//look at each tag,
            foreach(var tag in allTags)
            {
            //if it is the first occurrence of the tag, add a new property with that tag and a count of one
                if (!tagNames.Contains(tag.Text))
                {
                    tagNames.Add(tag.Text);
                    tagCounts.Add(1);
                }
            // if it is already present, increase the count by one
                else
                {
                    int index = tagNames.IndexOf(tag.Text);
                    tagCounts[index]++;
                }
            }

I then have two lists, where the name and count are on the same index for each.  

We'll send these along to the view in our viewmodel.

Part 3: Creating a SharpScript Monster

Part 3: Creating a Sharpscript Monster


Now over in the view, I'm frankenstiening together C# and JS, using the dereference operator ( @: ) to make 2 javascript arrays that hold the data I'll need to have in an array of objects.  To me this seems a LOT easier than converting C# objects to JSON objects, but let me know if you find a better way in the comments!

When we finally create an object, we make sure and pass along the url as an interpolated string, so that users can click on the tag and reach the proper page.


@section Scripts{
    <script>
        let tagArray = []
        @foreach (var tagName in Model.TagNames)
        {
            @:tagArray.push("@tagName")
        }
        let tagCount = []
        @foreach (var count in Model.TagCounts)
        {
            @:tagCount.push(@count)
        }
        for (var i = 0; i < tagCount.length; i++) {
            cloudArray.push({text: tagArray[i], count: tagCount[i],url: `/Posts/TagIndex?tagText=${tagArray[i]}`})
        }
        console.log(cloudArray)
    </script>
}


Check your browsers console to see if you got the desired result, and then either comment out or delete the console.log line.

Part 4: getting it functional

After we import d3 and d3-cloud via a cdn, we need to import our js file.  
I pasted the stackoverflow code right into the JS file, to begin making edits.  

The first issue is that somewhere along the way, category20() became deprecated.  So after some digging into the color scheme issue, I found we could fix this in a simple way.  I went on Adobe Colors to find some matching colors for my site, and constructed an array of those hex values. 

var blueColors = ["#394166", "#889BF2", "#5d6ba7", "#6472B3", "#4f5a8c"]
Then, down where the .style("fill".....) happens, I replaced the original line with this:

        .style("fill", function (d, i) { return blueColors[i % 5]; })
This would take the index of each tag and assign one of the five colors to it.  

The next step was to adjust the width and height of the svg to match my site, as well as appending it to a div with id "#tagCloud" to ensure I had control over the placement, instead of just sticking it at the bottom of my page.  

To add the link, I edited the final method (.on) to use the url of each tag.  I removed the "_blank" target as well, since it is keeping the user in my site.  It now looks like this:

.on("click", function (d, i) {
            window.open(i.url);
        });

While these steps made the word cloud functional, and we could certainly stop here, We should spruce it up a bit.

Part 5: finishing touches

While it would be nice to make it responsive, all the solutions I've tried for putting a d3 svg in a responsive element have crashed and burned at the moment.  I'm adding two classes to the svg to hide the layout when on small or medium screens.  

    d3.select("#tagCloud")
        .append("svg")
        .attr("class", "d-none")
        .attr("class", "d-lg-inline")
        .attr("width", width)

If anyone knows of an actual fix for this issue let me know in the comments.

I'd also like a little more interactivity, so I'm going to make the elements grow slightly when you hover over them.  

Adding 2 more .on events will do the trick, feel free to adjust the scale to fit your site.

.on("mouseover", function (d, i) {
            d3.select(this)
                .attr("transform", function (d) {
                    return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")scale(1.4)";
                })
            
        
        }) 
        .on("mouseout", function (d, i) {
            d3.select(this)
                .attr("transform", function (d) {
                    return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
                })
        })
        

Finally lets smooth out the transition by adding this css class to the words:

.tagWord{
    transition: all 500ms ease
}

And don't forget: 

.attr("class", "tagWord")


That was a lot of work, and a lot of learning, but now my site is more flashy, and a little more fun!


Tags:
Login to add a comment

0 Comments