I recently open sourced and launched version 1.0 of Color Legend Element, a Custom Element that’s intended to be used as a legend with data visualizations. I’m fairly satisified with its current functionality which covers common use cases when visualizing continuous, discrete, or categorical data. It’s API (attributes and properties) makes it fairly straight forward to use (IMHO) and it is well documented in the CLE website, Observable Notebook, and Github repository.
An important part of creating the CLE for me was making it simple to use. You can read more about its usage in the links mentioned above, but the gist of it is:
- include the script tag
- declare the HTML
Which then gives you:
The most important attributes are
range which determine how the CLE renders. The above CLE example is rendered in the DOM of this webpage (as are others that follow). Try opening your browser’s developer tools and poking around at it!
Creating legends for data visualizations is one of those pesky yet necessary tasks that I tend to find to be mundane and repetitive. Often it’s a simple enough of an exercise to write a legend that’s paired with a specific visualization that it doesn’t feel worth the time to abstract it into a reusable component. Yet each time I find myself creating a legend I notice the repetition and it starts to feel like a bit of a time sink. By the time I’m done, I lament “why haven’t I made this reusable yet?”.
When using Observable Notebooks for exploring and prototyping data visualizations, one has access to such a reusable legend component. The Color Legend (which CLE is directly influenced by) was written by Mike Bostock, one of the founders of Observable and primary authors of D3JS. Its implementation assumes you’re already using D3JS, and accepts a
d3-scale as input, plus additional configuration options as needed. It renders an SVG legend based on the type of scale as well as its domain and range. It was an epiphany when I began using Mike’s Color Legend; I realized how great it was to not have to write a legend from scratch as it meant more time to focus on the visualization I was working on. The only issue was using it outside of Observable.
I do want to mention that CLE shouldn’t discount previous legend components or libraries out there. One of which is Susie Lu’s D3 SVG Legend library, which follows D3’s style of method chaining and reusing scales. The D3 SVG Legend implementation also covers more use cases than CLE currently does, such as graduated circles. My intention with CLE is to limit its functionality to using color as the primary form of visual encoding and to decouple it a bit more from D3 (even though it still requires a handful of D3 modules as dependencies).
Why Web Components?
By choosing Web Components as a framework I hope that the CLE can be useful to more folks on the web, rather than being limited to those who use a particular JS framework, and to not exclude those who prefer not to use a JS framework.
Lit & Lit Element
While it’s certainly possible to use Web Components via the aforementioned native DOM APIs alone, I found that it is a better developer experience to use a Web Component “wrapper library” such as Lit. Aside: I’m not sure if “wrapper library” is the right terminology here, but I think it describes these sorts of Web Component libraries well enough, because ultimately from my understanding they compile down to what the browser sees as native Web Components.
Why did I choose Lit vs. some other Web Component library? Well Lit happens to be a Web Component library developed at Google, so it was the one I learned while working there and thus am biased towards it. I haven’t tried other Web Component libraries like Stencil, nor have I tried using Svelte to create a Custom Element, so I can’t honestly or authoratively give an valid comparison of Web Component libraries. There are a ton of ways to make a Web Component right now. Here’s an article that summarizes the tech choices (fifty and counting!) you have when working with Web Components, in case you’re interested.
The benefit of using Lit is that it handles a lot of things for you out of the box that you otherwise would need to implement yourself, such as:
- re-renders when attributes / properties change (reactivity)
- declarative templating
- enabling the Shadow DOM by default to encapsulate CSS
- compiles to standard Custom Elements
Lit is a tiny library, around 5 KB (minified and compressed), much smaller than your average JS framework. While it’s most typically used for creating individual components, you can also leverage it to write entire web-apps. Lit can
import and render other (web) components enabling you to create a UI entirely consisting of Web Components. You can even use your favorite state management library as well if you like.
Styling with CSS Variables
An interesting aspect of Web Components that I learned while creating CLE was providing a structured mechanism for CSS style overrides. While the main legend area is rendered via CLE’s attributes/properties, changing the style of its other internals like fonts, border, and background color reside in the realm of CSS. A result of Shadow DOM encapsulation is that applying styles to the CLE like you would with normal HTML elements doesn’t work.
For example, the following style rule would have no effect on the CLE’s default font-family, which is sans-serif:
One simple solution to this problem is to use CSS variables (Custom Properties). Just about every style property of the CLE may be overriden using a CSS variable. For example, here’s how the same style rules above could be applied using CSS variables:
A big thanks to Nolan Lawson for his write up on Options for Styling Web Components, which was influential for how I decided to expose CLE style overrides. You can see the full list of CSS Variables for the CLE in its Readme file on Github.
Inserting Child Content using Slots
I anticipated CLE’s markup not suiting every possible legend use case, so I decided to take advantage of the HTML slot element to enable the rendering of child elements. The CLE has two named slots: one for a “subtitle” that fits between the title and legend area, and another for a “footer” that fits below the legend area. Here is how they are used:
= No data
One thing that’s interesting to note about slotted elements is that they are considered part of the “light DOM” and as such may be styled by any global CSS. For example, here’s the CSS used for the above slots demo:
I didn’t get around to everything I would have liked to for the v1 release of Color Legend Element. Here are some ideas for updates I have in mind.
After announcing CLE on Twitter, someone quickly pointed out that relying on color alone does not accomodate users who have color deficiencies with their vision. One way of accommodating this is by using patterns and symbols in addition to or in place of color. This feature would be a helpful A11Y improvement for the CLE’s categorical, discrete, and threshold scale types.
Another trickier piece of A11Y that I have been anticipating is making the SVG elements accessible, which I elaborate on further in the event handlers idea below. This is not trivial and would require some research and user testing to get right.
At the bare minimum the CLE should have a way of providing “alt” text that describes the legend. An example alt text might read “a graduated color bar transitioning from yellow to green to blue, with a value of zero at yellow, fifty at green, and one hundred at blue.” This could also work for discrete and threshold scales, where alt text could describe the bin values and color for each SVG
rect element. Alt text could easily be passed in as an attribute / property, it would be up to the user of the CLE to apply the correct alt text.
More Legend Types
Currently the CLE does not support the full range of scales available in the d3-scale library. Given that CLE is intended to be simple to use, I’m not sure supporting every type of D3 scale would make sense. I am however interested in adding support for a few more scale types such as Diverging and Logarithmic scales, which I feel are common enough in data visualizations that they’re worth supporting. These would render similarly to the CLE’s existing “continuous” scale type, but would utilize D3’s diverging and logarithmic scales for placement of the axis ticks.
Another option would be to expose the CLE’s internal
Interactive legends can really help make a data visualization shine. For example, they can act as filters to enable a user to toggle various categories or groupings of data “on” and “off” in the visualization. Unfortunately the CLE currently does not have any kind of event handling, and while it wouldn’t be too difficult to add event handlers for things like clicking on and mousing over the legend, it would be tricky to make those handlers accessible. It would mean applying roles and ARIA correctly, as well as implementing keyboard navigation. These tasks can be tricky to get right for users of assistive technology when it comes to SVG. It’s not to say I’m not up for the challenge, but time is a factor.
That about sums up Color Legend Element. Please make sure to check out the CLE website and Observable Notebook for examples on how to use it. To report a bug or make a suggestion, please open an issue in the Github repository or send me a Tweet. Lastly, please do let me know if it’s helped you out at all in a project, I would be flattered to see it out in the wild! Thanks for reading 🙏.
If you found this website to be helpful please consider showing your gratitude by buying me a coffee. Thanks!
Web-Components Lit Lit-Element D3JS TypeScript