I'd also argue that D3 is no more verbose than vanilla JS (at least for this example). What's the alternative for creating a line in SVG?
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line')
line.setAttribute('x1', ...)
line.setAttribute('y1', ...)
line.setAttribute('x2', ...)
line.setAttribute('y2', ...)
// etc
document.querySelector('svg').appendChild(line)
The `.selectAll().data().join()` data binding method (or `.enter()` on older versions) is very intuitive once you understand it, but for the layman coming in, it's inaccessible AF. I fudged a little in my explanation to make it more accessible. But hey. That's learning.
That's like saying that your car is no slower than walking. You want your visualisation library to be a big improvement on not having one, not "no worse".
Right? So why load a bunch of JS to do the same thing? One step further, why load any JS at all since you're just generating an SVG? People have forgotten that HTML and SVG are meant to be DATA containers, you don't have to use JSON + JS.
D3 is cool for the advanced visualizations and the interactivity. If you're sticking to static graphs, for the love of god just have the server serve a static SVG.
My main takeaway from so much of this is that "just a chart" is one of the biggest sources of hidden complexity in displaying useful information to people. It's right up there with "a simple web form" and "a web page with some simple interactivity."
Everybody has a wildly different idea of what good looks like. Defaults will never be right. Personal and global taste changes annually. We clown react (rightly) for constantly reinventing the same 4 wheels, but customers gleefully use new stuff all the time.
It's kind of amazing that d3 has been so durable in the frontend world. It really is a wrapper over a pretty solid approach. And yeah, that approach is complex, but that's the reality of visualization. It's hard to imagine another one that's that good.
All other libraries will just have a pile of abstractions that will leak everywhere as soon as you deviate from the happy path.
If you just want a bunch of auxillary charts and don't need a ton of control, just use something like ECharts. When you want real creative control over your visualisations, don't bother with anything high level.
I tried for a few years to help Streamlit deployments in production. Never will go that direction again. Litestar + React with echarts or d3 is the way to go. Or use your favorite application backend, but REST is almost certainly the way your use case needs to go.
I just had this happen to me with something I had invested considerable time into. I've finally found a workaround, and even leveraged some of the previous tech, but man it sucks to have something just become abandonned. All the more reason to choose boring tech when you can...
Anyway my latest startup is trying to make AI do migrations, in no small part because of this problem 15 years ago.
It clearly has some bugs (like the score sometimes being NaN - no idea how I messed that up), but I haven't touched the code in over a decade, so it's a little time capsule.
I am still proud of the D3 gadget I made about 8 years back as a green web dev. Couldn't have made it any other way, not sure if I could with any other library today. Wouldn't want to do it again, though, unless I was a dedicated front-end guy.
[0] At least in the core, I'm not too familiar with the full ecosystem and what is considered official in terms of plugins. Everytime I've tried to use it, I've not found the documentation leading me to using anything more specifically oriented towards charting.
You can simply just use Tableau or Power BI and take screenshots otherwise.
- d3-array: data processing
- d3-scale: mapping from data attributes to visualization encodings
- d3-scale-chromatic: color schemes
- d3-shape: defining the shapes of paths, such for a line chart
- d3-color: manipulating colors
- d3-time: manipulating dates
- d3-format: formatting numbers
- d3-time-format: formatting dates
These modules are useful even if you don't use D3 to manage the DOM. For example, you can use these utilities to help calculate all of the attributes for your SVG elements, but then use Svelte/React/Solid/Vue to create and render the SVG.
If I'm building a visualization inside of a web app, I tend to prefer this approach since it leaves the framework in control of the DOM throughout the whole app. It feels simpler, especially if I want interactions in the visualization to modify the app's state.
I suppose it matters less now in the LLM age.
Once your LLM gets too expensive, goes out of business, and the competitors just don't quite do it the way your favorite LLM does it, you have a problem.
Whereas once the D3 training wheels come off, its muscle memory is hard to shed.
Also, I've come to really dislike libraries that use this kind of `a.b().c()` chained form:
boxplotContainer
.append("line")
.attr("x1", xScale(gender) - boxplotWidth/2)
.attr("x2", xScale(gender) + boxplotWidth/2)
I find it hard to reason about. What is the return value of boxplotContainer.append("line")? How do I debug it? What values can I inspect?That, and I really never got comfortable with the `.enter()` and `.exit()` and `.join()` concepts. It's so abstract.
I don’t know D3, but usually in these chaining approaches it would be something like:
boxplotContainer
.append("line")
.debug(callable_thing)
.attr(…)
Where debug() might be pipe() or some variation that lets you provide a callable where you can inspect the data (or drop into an interactive REPL, or whatever).Another thing worth mentioning that newcomers seem to take for granted - the margin boilerplate required for correct positioning (see https://observablehq.com/@d3/margin-convention).
https://blog.frankel.ch/learning-clojure/2/
Something like a(b(c(d(e(7)))))) in Javascript could be written (-> 7 e d c b a) in Clojure?
According to the other comment it seems he meant the |> pipe operator that is under proposal in js
(Trust me. I don't know jack about JavaScript, I had to get through the MDN docs to understand what they were, and once I did, made a whole lot more sense).
JS does not have a straightforward equivalent. The old and deprecated `with` keyword might seem similar but it's only a surface resemblance as it does not perform the return-value threading that makes the above pattern useful, it was meant for methods that mutate object state. There's a TC39 proposal[2] to add a pipe operator that would accomplish a similar thing to threading macros via an infix operator but it's still a draft.
Mike Bostock is an interesting person, and a case study in why we don't design languages from a single person's genius.
Francisco was working on an in-browser slideshow tool like Google Slides, but in the late 00s. To power it, he invented his own language: Objective-J. It had its own toolchain, in the days when most developers were just writing JavaScript inline or maybe in .js files.
Remember, it took React a few years to catch on because it required command line tools to translate JSX to JS. This had the same friction, but years earlier.
This was the thesis behind Objective-J: In the late 1900s, C was the foundation of a good language, but it needed another layer of abstraction on top of it to be more ergonomic. The developers at NeXT/Apple built that layer and called it Objective-C. Objective-J saw the same potential in JavaScript, and ported the ergonomics of Objective-C atop it. It basically tried to do for JavaScript what Objective-C had done for C.
This was the critical part of the argument:
JavaScript (in the era before modules, => functions, and the other current niceties) was the core of a useful language, but it needed a more ergonomic layer on top. Libraries like jQuery were building de-facto DSLs, but calling them libraries. Objective-J was taking conceptual responsibility for being a language on top of JavaScript, instead of being "just" a library. By owning up to being its own language, they could take syntactical liberties that they couldn't under the constraints of being just another JS library.
Semantics matter more than literals sometimes.
*EDIT: Grammar. I was typing on my phone. Soz.
Why??
Everything in real life uses bottom left for 0,0! Probably because the first EGA/VGA accelerators worked this way to save one instruction in the most common use case and things never change....
CRTs scanned downwards, most people read downwards.
I would say that nearly everything in the real world (outside math) especially computer related uses top left as an origin.
I concede that point, its not want to meant, but I did express my point poorly.
My point is:
- A lot of things work like books[1]: top-left to bottom-right
- However, 99+ % of graphs (anything from revenue numbers to health data) in all mediums (books, newspapers, websites) use a bottom-left origin. Never seen a revenue bar growing from top to bottom. In fact, I dont remember the last time i have seen a graph not using the I. Quadrant. Do you?
- D3.js (imo) is a lib to create graphs from data, but does not abstract this problem away? Why?
[1] "western/latin"-style books ofc[1] and before that, a "every screen is a CRT TV with a photon gun that 'draws' lines, starting in the top left"-thing
"Everything in real life" made me laugh out loud.
Second of all, isn't it ungodly slow? I get that it can draw a few boxes nicely, and maybe shuffle them around, but I had to write my own engine using html canvas because d3 couldn't get svg to flow properly if I had thousands of pixels in my image.
Honestly, if you're going to go through the trouble of understanding d3, I would just write your own javascript canvas to animate things.