Case Study 2: The matplotlib 2.0 Default Style Overhaul
In January 2017, matplotlib 2.0 was released. It looked substantially different from matplotlib 1.x. The default colors had changed. The default fonts had changed. The default gridlines had changed. For matplotlib users, this was the biggest visual change in the library's history. For the core developers, it was the resolution of a decade-long tension between preserving backwards compatibility and admitting that matplotlib's defaults were, by contemporary standards, ugly.
The Situation
By 2016, matplotlib had been around for 13 years. The library was mature, widely used, and the de facto standard for Python plotting. It was also starting to look dated. The default colors — a saturated blue, a bright green, a crimson red — had been chosen in the early 2000s to match MATLAB's defaults, which in turn had been chosen in the 1980s for CRT displays. They were visually harsh on modern LCD screens, they were not colorblind-safe, and they did not match the aesthetic conventions of modern data journalism and data science.
The gridlines were heavy and black. The default font was DejaVu Sans, a fallback font chosen for cross-platform availability, not for aesthetic quality. The spines were all four present, creating a boxed look that had fallen out of fashion. The tick marks were long and heavy. Every default in matplotlib 1.x reflected early-2000s design conventions and felt, to 2016 eyes, cluttered and amateurish.
Newer libraries like seaborn (built on top of matplotlib) provided cleaner defaults through their own style sheets. Users who wanted a modern look routinely applied seaborn's theme to matplotlib charts, or used ggplot2 (in R) instead. Matplotlib's own defaults had become a barrier to adoption for users who could not figure out how to override them.
The core developers knew this. They had known it for years. The reason they had not fixed it was a tension that matters in every long-lived project: backwards compatibility. matplotlib had millions of users, tens of thousands of tutorials, and thousands of published papers whose figures were generated with specific style assumptions. Changing the defaults would cause existing code to produce different output, which would be a form of "breaking" the library even though the API was unchanged.
For years, the compromise was to leave the defaults alone and encourage users to apply style sheets. This worked but was unsatisfying — new users who had never heard of style sheets were getting the ugly output, and the library's reputation suffered. In 2016, the core team decided the cost of inaction was greater than the cost of breakage. They began planning a major default-style overhaul for the 2.0 release.
The project took months of design discussion, community feedback, and iteration. The team hired a designer (Tony Yu, who had been working on the seaborn-to-matplotlib compatibility) to lead the color palette selection. They ran design reviews on GitHub. They tested color choices against colorblind simulation tools. They negotiated specific values for fonts, line weights, and gridline colors. And in January 2017, matplotlib 2.0 was released with a substantially new default style.
The release was, by any measure, a success. Existing users adapted quickly (the API was unchanged, so existing code still worked, just with different colors). New users got a cleaner, more modern default out of the box. The library's reputation improved. And the process set a precedent for how matplotlib could evolve its design choices over time without breaking the API.
This case study is worth examining in Chapter 12 because it illustrates, at the library level, the same principles you apply at the chart level: style decisions are deliberate, they reflect design principles, and they compound across many uses. matplotlib 2.0's default style is, in effect, a pre-packaged style system for Python visualization. Understanding what changed and why teaches the same lessons that building your own style system teaches.
The Data (the Design Decisions)
The matplotlib 2.0 release changed many defaults. The most visible changes:
1. The default color cycle. Matplotlib 1.x used a sequence starting with saturated primary colors ("b" for blue, "g" for green, "r" for red, "c" for cyan, "m" for magenta, "y" for yellow, "k" for black). Matplotlib 2.0 switched to the "tab10" palette (originally from Tableau's chart library), which starts with more subdued hues: #1f77b4 (a muted blue), #ff7f0e (orange), #2ca02c (a greenish teal), #d62728 (a muted red), and so on. The new palette is colorblind-distinguishable (at least for deuteranopia and protanopia), less visually harsh, and more aligned with modern data journalism conventions.
2. The default colormap. matplotlib 1.x used "jet" as the default colormap for scatter plots, imshow, and other continuous-color functions. jet is a rainbow colormap that Chapter 3 (and most visualization experts) identify as perceptually broken — equal steps in data do not correspond to equal perceptual changes, and the luminance is non-monotonic. Matplotlib 2.0 switched the default to "viridis," a perceptually uniform colormap designed specifically for scientific visualization by Stéfan van der Walt, Nathaniel Smith, and Eric Firing. The change from jet to viridis was one of the most significant visual improvements in the release.
3. The default font. matplotlib 1.x used Bitstream Vera Sans as the primary font, with DejaVu Sans as a fallback. Matplotlib 2.0 made DejaVu Sans the primary font (a pragmatic choice because DejaVu is more widely installed and has better international character support). The font is not beautiful, but it is consistent across platforms, which was the priority.
4. Line widths and marker sizes. matplotlib 1.x used thin lines (default 1.0 point) and small markers. Matplotlib 2.0 increased the default line width to 1.5 points, which is more visible on modern high-DPI displays. Marker sizes were similarly increased.
5. Gridlines. matplotlib 1.x drew gridlines in black by default, which competed visually with the data. Matplotlib 2.0 changed the default grid color to a lighter gray and reduced the line width, making the grid recede behind the data. Gridlines also became off by default for most chart types (requiring an explicit ax.grid(True) call to turn them on).
6. Spines. matplotlib 1.x drew all four spines by default. Matplotlib 2.0 still draws all four by default — the core team decided that removing spines was too aggressive a change — but they lightened the spine color slightly and made spine removal easier through rcParams.
7. Tick direction and length. matplotlib 1.x had long outward ticks. Matplotlib 2.0 shortened them and changed their direction to outward by default (the default was inward in 1.x), which is more readable on modern displays.
8. Figure backgrounds. matplotlib 1.x sometimes used a gray background. Matplotlib 2.0 standardized on white backgrounds for cleaner appearance.
Each of these changes was small on its own. Together, they produced a visibly different default appearance — cleaner, more modern, more aligned with contemporary design conventions.
The Process
The matplotlib 2.0 style overhaul was a public, collaborative design process. Key elements:
Community feedback. The core team opened GitHub issues to discuss specific changes. Users posted example charts, voiced preferences, and debated the trade-offs. The process was transparent, and anyone with a GitHub account could participate.
Color palette selection. Tony Yu led the color palette work, testing multiple candidate palettes against colorblind simulation tools and soliciting feedback from the community. The eventual choice — tab10, from Tableau — was a compromise between "new and better" and "familiar to existing users."
The viridis colormap. The switch from jet to viridis was championed by Stéfan van der Walt and Nathaniel Smith, who had given a famous SciPy 2015 talk arguing for perceptually uniform colormaps. Their work had already influenced the broader scientific visualization community, and the matplotlib 2.0 release was the moment when their argument became the default.
Backwards-compatibility concerns. The team worked hard to minimize breakage. The specific concern was that existing code that had been tuned for matplotlib 1.x defaults would look slightly different with 2.0 defaults. The team addressed this by documenting the specific changes, providing a "classic" style sheet (plt.style.use("classic")) that restored the 1.x defaults, and allowing users to opt out of the new style if they needed pixel-perfect reproducibility with their old code.
Public communication. The release was accompanied by blog posts, documentation updates, and conference presentations explaining the changes. The matplotlib team understood that a default-style change needed to be announced clearly so users knew what to expect.
Iteration after release. Matplotlib 2.0 was the start, not the end. Subsequent releases (2.1, 2.2, 3.0, 3.1, and beyond) refined the defaults based on user feedback — tweaking specific colors, adjusting line weights, updating the cycler default. The initial 2.0 release set a new baseline, and the team continued to improve it over time.
The Impact
The matplotlib 2.0 release had several measurable effects:
1. Improved visual quality of default charts. The most immediate effect: every new matplotlib chart produced by users who had not customized their defaults now looked better. This affected thousands of tutorials, millions of notebooks, and countless published figures. The improvement was small per chart but large in aggregate.
2. Reduced pressure to use external style libraries. Before matplotlib 2.0, many users applied seaborn's theme to matplotlib to get a cleaner look. After 2.0, the matplotlib default was cleaner, and fewer users needed to reach for external styles. seaborn remained useful for its high-level APIs, but as a styling layer, it was less necessary.
3. Alignment with modern visualization conventions. The new defaults were more aligned with what professional data journalism, scientific publications, and data science workflows were already doing. matplotlib stopped feeling like a 2003 library.
4. Precedent for future design evolution. The successful 2.0 release showed that matplotlib could change its defaults without breaking users. This made subsequent small refinements easier — the team could argue "we already changed the defaults once and it worked fine" when proposing new refinements.
5. Preservation of backwards compatibility for those who needed it. The "classic" style sheet meant that users who had carefully tuned their code for matplotlib 1.x could still reproduce their old output. This compromise kept the update from being fully disruptive.
6. Increased user satisfaction. Anecdotal evidence — Stack Overflow questions, blog posts, Twitter reactions — suggested that matplotlib's reputation improved after 2.0. The library was still matplotlib (quirky, powerful, sometimes frustrating), but it no longer looked like it had been designed for a CRT monitor.
7. Influence on other libraries. Libraries built on top of matplotlib (seaborn, pandas plot methods, geopandas, and others) automatically inherited the new defaults. The improvement cascaded through the entire Python visualization ecosystem.
Why It Worked: A Theoretical Analysis
The matplotlib 2.0 release succeeded for reasons that connect to the chapter's themes.
1. The defaults embodied design principles. The specific changes — tab10 over saturated primaries, viridis over jet, lighter gridlines, shorter ticks — reflected principles from visualization research: colorblind safety, perceptual uniformity, visual hierarchy, reduced chart-junk. The defaults were not arbitrary aesthetic preferences; they were a coded version of design principles that had become consensus in the field.
2. The change was systematic. The team did not tweak one chart type at a time. They changed the underlying rcParams that affect every chart, and the result propagated automatically to every plotting method. This is the same systematic approach that Chapter 12 advocates at the individual level, applied at the library level.
3. Backwards compatibility was respected where it mattered. The API did not change. Existing code still ran. Users who needed pixel-perfect reproduction could opt into the classic style. This is what professional software development looks like: you improve the defaults, but you preserve the escape hatch for users who depend on the old behavior.
4. The process was transparent and collaborative. Community input shaped the specific decisions. Multiple people contributed to the design. The discussion was public. This is how open-source projects should make visible design decisions — in the open, with input from users.
5. Documentation was clear and prominent. The team did not rely on users to discover the changes. They wrote blog posts, updated tutorials, and included the changes prominently in the release notes. Design changes need explanation, and the explanation has to be discoverable.
6. The change was one moment, not a series. The team made many changes at once rather than rolling them out individually. This is sometimes easier for users because they only have to adapt to a new style once, rather than getting a slightly different default with every release. A single coordinated change is often less disruptive than a series of small changes.
7. It followed principles from visualization research. The specific choices (colorblind safety, perceptual uniformity, reduced visual noise) followed the visualization research that Chapter 2, 3, and 6 referenced. The team read the research and implemented it. This is what applying design principles looks like in practice.
Complications and Criticisms
The matplotlib 2.0 release was mostly well-received but not universally praised. Several criticisms are worth noting.
The spines were not removed by default. Some users (including the author of this textbook) would have preferred that matplotlib 2.0 remove the top and right spines by default. The core team decided this was too aggressive a change because it would break too many existing charts that were carefully positioned with the spine layout. A more incremental change (removing only the top spine, for example) was debated but rejected. The result is that matplotlib's defaults are still not as clean as they could be.
The font choice was a compromise. DejaVu Sans is not a beautiful font. It is functional and consistent across platforms, but it does not match the aesthetic quality of professional typefaces like Inter or Source Sans Pro. The choice was made for reasons of availability, not aesthetics. Users who want a better font still have to override the default.
Some users preferred the old colors. A minority of users genuinely liked the saturated primary colors and felt the new muted palette was less expressive. The classic style sheet exists partly to accommodate this preference.
The changes were not backported. matplotlib 2.0 was the first version with the new defaults; matplotlib 1.x was frozen. Users on older matplotlib versions did not get the improvements. This is normal for library updates but meant that the benefit propagated slowly through the ecosystem.
Backwards compatibility is not perfect. Even with the classic style sheet, some code produced subtly different output under 2.0 than under 1.x because the internal rendering pipeline had changed in small ways. Users who relied on exact pixel reproduction had to update their code.
The rainbow colormap is still available. The default changed from jet to viridis, but jet was not removed. Users can still use cmap="jet" if they want. Some visualization researchers argued that jet should be deprecated entirely to prevent its continued use in publications, but the core team kept it for backwards compatibility.
Lessons for Modern Practice
The matplotlib 2.0 release teaches several lessons for your own style system work.
1. Defaults matter. The single biggest source of chart ugliness is bad defaults. If you are building a style system, spend time on the defaults — they are what users will see most often. The matplotlib team spent months on their default choices because they understood the defaults were the single most important thing.
2. Systematic changes compound. A small change to the default color cycle, applied to every chart in the ecosystem, has more impact than elaborate customization of individual charts. The same principle applies at the individual level: building a good default rcParams and applying it to every chart is more valuable than tweaking each chart separately.
3. Respect backwards compatibility where it matters. When you update your style system, provide an escape hatch for users (or future you) who depend on the old behavior. The matplotlib "classic" style sheet is a model for how to preserve backwards compatibility without freezing the defaults.
4. Follow visualization research. The matplotlib 2.0 changes were not arbitrary aesthetic preferences. They were based on research: colorblind safety testing, perceptual uniformity work, visualization literature. When you make style decisions, ground them in research or documented principles, not personal taste alone.
5. Document your changes. The matplotlib team wrote blog posts, tutorials, and release notes explaining the 2.0 changes. If you update your personal style system, document what changed and why. Future-you will forget, and documentation is how you remember.
6. Iterate over time. matplotlib 2.0 was the start of a process, not the end. Subsequent releases refined the defaults. Your own style system should evolve similarly — revisit it periodically, refine based on experience, update as design conventions change.
7. Build on a foundation others can extend. The matplotlib 2.0 defaults are used as a foundation by seaborn, pandas, geopandas, and countless other libraries. A well-designed style system creates a foundation that others can build on. Your own style system might be smaller in scope, but the same principle applies: if your defaults are good, other tools that use matplotlib will inherit your improvements.
8. The community has taste. The matplotlib 2.0 process involved community feedback, not just core team preferences. The visualization community has developed opinions about what looks good, what does not, and why. When you make style decisions, solicit feedback from other practitioners — their perspectives will catch things you miss.
Discussion Questions
-
On the spine decision. matplotlib 2.0 did not remove the top and right spines by default, even though removing them is a standard Chapter 6 declutter move. Was this the right call? What would be the cost of removing them in a future release?
-
On backwards compatibility vs. good defaults. matplotlib's team preserved backwards compatibility through the classic style sheet. When are backwards-compatibility concerns justified, and when do they become an excuse to avoid necessary improvements?
-
On the jet colormap. matplotlib 2.0 changed the default from jet to viridis but kept jet available. Should deprecated colormaps like jet be removed entirely to prevent their continued use? What are the trade-offs?
-
On community-driven design. The matplotlib 2.0 changes involved community feedback through GitHub. Is this the right process for design decisions, or should a smaller group make the decisions more quickly?
-
On the role of research. The changes were informed by visualization research (perceptual uniformity, colorblind safety, etc.). Should library design decisions always be grounded in research, or is practitioner taste also legitimate?
-
On your own defaults. If you were building matplotlib's defaults from scratch today (with no backwards compatibility concerns), what would you change from the 2.0 defaults? What principles would guide your choices?
The matplotlib 2.0 release is a rare case study: a major open-source library changing its default style in a public, documented, principled way. The lessons apply at every scale. When you build your own style system, the specific choices you make are less important than the process: ground the choices in principles, apply them systematically, document the rationale, preserve escape hatches, iterate over time, and share the work publicly if you can. matplotlib's core developers did this, and the result is the default you use every day. You can do the same for your own charts.