A place to cache linked articles (think custom and personal wayback machine)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

преди 5 години
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang=fr>
  4. <!-- Has to be within the first 1024 bytes, hence before the <title>
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset=utf-8>
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name=viewport content="width=device-width,minimum-scale=1,initial-scale=1,shrink-to-fit=no">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>What Makes Software Good? (archive) — David Larlet</title>
  13. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  14. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons/apple-touch-icon.png">
  15. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons/favicon-32x32.png">
  16. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons/favicon-16x16.png">
  17. <link rel="manifest" href="/manifest.json">
  18. <link rel="mask-icon" href="/static/david/icons/safari-pinned-tab.svg" color="#5bbad5">
  19. <link rel="shortcut icon" href="/static/david/icons/favicon.ico">
  20. <meta name="apple-mobile-web-app-title" content="David Larlet">
  21. <meta name="application-name" content="David Larlet">
  22. <meta name="msapplication-TileColor" content="#da532c">
  23. <meta name="msapplication-config" content="/static/david/icons/browserconfig.xml">
  24. <meta name="theme-color" content="#f0f0ea">
  25. <!-- That good ol' feed, subscribe :p. -->
  26. <link rel=alternate type="application/atom+xml" title=Feed href="/david/log/">
  27. <meta name="robots" content="noindex, nofollow">
  28. <meta content="origin-when-cross-origin" name="referrer">
  29. <!-- Canonical URL for SEO purposes -->
  30. <link rel="canonical" href="https://medium.com/@mbostock/what-makes-software-good-943557f8a488">
  31. <style>
  32. /* http://meyerweb.com/eric/tools/css/reset/ */
  33. html, body, div, span,
  34. h1, h2, h3, h4, h5, h6, p, blockquote, pre,
  35. a, abbr, address, big, cite, code,
  36. del, dfn, em, img, ins,
  37. small, strike, strong, tt, var,
  38. dl, dt, dd, ol, ul, li,
  39. fieldset, form, label, legend,
  40. table, caption, tbody, tfoot, thead, tr, th, td,
  41. article, aside, canvas, details, embed,
  42. figure, figcaption, footer, header, hgroup,
  43. menu, nav, output, ruby, section, summary,
  44. time, mark, audio, video {
  45. margin: 0;
  46. padding: 0;
  47. border: 0;
  48. font-size: 100%;
  49. font: inherit;
  50. vertical-align: baseline;
  51. }
  52. /* HTML5 display-role reset for older browsers */
  53. article, aside, details, figcaption, figure,
  54. footer, header, hgroup, menu, nav, section { display: block; }
  55. body { line-height: 1; }
  56. blockquote, q { quotes: none; }
  57. blockquote:before, blockquote:after,
  58. q:before, q:after {
  59. content: '';
  60. content: none;
  61. }
  62. table {
  63. border-collapse: collapse;
  64. border-spacing: 0;
  65. }
  66. /* http://practicaltypography.com/equity.html */
  67. /* https://calendar.perfplanet.com/2016/no-font-face-bulletproof-syntax/ */
  68. /* https://www.filamentgroup.com/lab/js-web-fonts.html */
  69. @font-face {
  70. font-family: 'EquityTextB';
  71. src: url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff2') format('woff2'),
  72. url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff') format('woff');
  73. font-weight: 300;
  74. font-style: normal;
  75. font-display: swap;
  76. }
  77. @font-face {
  78. font-family: 'EquityTextB';
  79. src: url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff2') format('woff2'),
  80. url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff') format('woff');
  81. font-weight: 300;
  82. font-style: italic;
  83. font-display: swap;
  84. }
  85. @font-face {
  86. font-family: 'EquityTextB';
  87. src: url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff2') format('woff2'),
  88. url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff') format('woff');
  89. font-weight: 700;
  90. font-style: normal;
  91. font-display: swap;
  92. }
  93. @font-face {
  94. font-family: 'ConcourseT3';
  95. src: url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff2') format('woff2'),
  96. url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff') format('woff');
  97. font-weight: 300;
  98. font-style: normal;
  99. font-display: swap;
  100. }
  101. /* http://practice.typekit.com/lesson/caring-about-opentype-features/ */
  102. body {
  103. /* http://www.cssfontstack.com/ Palatino 99% Win 86% Mac */
  104. font-family: "EquityTextB", Palatino, serif;
  105. background-color: #f0f0ea;
  106. color: #07486c;
  107. font-kerning: normal;
  108. -moz-osx-font-smoothing: grayscale;
  109. -webkit-font-smoothing: subpixel-antialiased;
  110. text-rendering: optimizeLegibility;
  111. font-variant-ligatures: common-ligatures contextual;
  112. font-feature-settings: "kern", "liga", "clig", "calt";
  113. }
  114. pre, code, kbd, samp, var, tt {
  115. font-family: 'TriplicateT4c', monospace;
  116. }
  117. em {
  118. font-style: italic;
  119. color: #323a45;
  120. }
  121. strong {
  122. font-weight: bold;
  123. color: black;
  124. }
  125. nav {
  126. background-color: #323a45;
  127. color: #f0f0ea;
  128. display: flex;
  129. justify-content: space-around;
  130. padding: 1rem .5rem;
  131. }
  132. nav:last-child {
  133. border-bottom: 1vh solid #2d7474;
  134. }
  135. nav a {
  136. color: #f0f0ea;
  137. }
  138. nav abbr {
  139. border-bottom: 1px dotted white;
  140. }
  141. h1 {
  142. border-top: 1vh solid #2d7474;
  143. border-bottom: .2vh dotted #2d7474;
  144. background-color: #e3e1e1;
  145. color: #323a45;
  146. text-align: center;
  147. padding: 5rem 0 4rem 0;
  148. width: 100%;
  149. font-family: 'ConcourseT3';
  150. display: flex;
  151. flex-direction: column;
  152. }
  153. h1.single {
  154. padding-bottom: 10rem;
  155. }
  156. h1 span {
  157. position: absolute;
  158. top: 1vh;
  159. left: 20%;
  160. line-height: 0;
  161. }
  162. h1 span a {
  163. line-height: 1.7;
  164. padding: 1rem 1.2rem .6rem 1.2rem;
  165. border-radius: 0 0 6% 6%;
  166. background: #2d7474;
  167. font-size: 1.3rem;
  168. color: white;
  169. text-decoration: none;
  170. }
  171. h2 {
  172. margin: 4rem 0 1rem;
  173. border-top: .2vh solid #2d7474;
  174. padding-top: 1vh;
  175. }
  176. h3 {
  177. text-align: center;
  178. margin: 3rem 0 .75em;
  179. }
  180. hr {
  181. height: .4rem;
  182. width: .4rem;
  183. border-radius: .4rem;
  184. background: #07486c;
  185. margin: 2.5rem auto;
  186. }
  187. time {
  188. display: bloc;
  189. margin-left: 0 !important;
  190. }
  191. ul, ol {
  192. margin: 2rem;
  193. }
  194. ul {
  195. list-style-type: square;
  196. }
  197. a {
  198. text-decoration-skip-ink: auto;
  199. text-decoration-thickness: 0.05em;
  200. text-underline-offset: 0.09em;
  201. }
  202. article {
  203. max-width: 50rem;
  204. display: flex;
  205. flex-direction: column;
  206. margin: 2rem auto;
  207. }
  208. article.single {
  209. border-top: .2vh dotted #2d7474;
  210. margin: -6rem auto 1rem auto;
  211. background: #f0f0ea;
  212. padding: 2rem;
  213. }
  214. article p:last-child {
  215. margin-bottom: 1rem;
  216. }
  217. p {
  218. padding: 0 .5rem;
  219. margin-left: 3rem;
  220. }
  221. p + p,
  222. figure + p {
  223. margin-top: 2rem;
  224. }
  225. blockquote {
  226. background-color: #e3e1e1;
  227. border-left: .5vw solid #2d7474;
  228. display: flex;
  229. flex-direction: column;
  230. align-items: center;
  231. padding: 1rem;
  232. margin: 1.5rem;
  233. }
  234. blockquote cite {
  235. font-style: italic;
  236. }
  237. blockquote p {
  238. margin-left: 0;
  239. }
  240. figure {
  241. border-top: .2vh solid #2d7474;
  242. background-color: #e3e1e1;
  243. text-align: center;
  244. padding: 1.5rem 0;
  245. margin: 1rem 0 0;
  246. font-size: 1.5rem;
  247. width: 100%;
  248. }
  249. figure img {
  250. max-width: 250px;
  251. max-height: 250px;
  252. border: .5vw solid #323a45;
  253. padding: 1px;
  254. }
  255. figcaption {
  256. padding: 1rem;
  257. line-height: 1.4;
  258. }
  259. aside {
  260. display: flex;
  261. flex-direction: column;
  262. background-color: #e3e1e1;
  263. padding: 1rem 0;
  264. border-bottom: .2vh solid #07486c;
  265. }
  266. aside p {
  267. max-width: 50rem;
  268. margin: 0 auto;
  269. }
  270. /* https://fvsch.com/code/css-locks/ */
  271. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  272. font-size: 1rem;
  273. line-height: calc( 1.5em + 0.2 * 1rem );
  274. }
  275. h1 {
  276. font-size: 1.9rem;
  277. line-height: calc( 1.2em + 0.2 * 1rem );
  278. }
  279. h2 {
  280. font-size: 1.6rem;
  281. line-height: calc( 1.3em + 0.2 * 1rem );
  282. }
  283. h3 {
  284. font-size: 1.35rem;
  285. line-height: calc( 1.4em + 0.2 * 1rem );
  286. }
  287. @media (min-width: 20em) {
  288. /* The (100vw - 20rem) / (50 - 20) part
  289. resolves to 0-1rem, depending on the
  290. viewport width (between 20em and 50em). */
  291. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  292. font-size: calc( 1rem + .6 * (100vw - 20rem) / (50 - 20) );
  293. line-height: calc( 1.5em + 0.2 * (100vw - 50rem) / (20 - 50) );
  294. margin-left: 0;
  295. }
  296. h1 {
  297. font-size: calc( 1.9rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  298. line-height: calc( 1.2em + 0.2 * (100vw - 50rem) / (20 - 50) );
  299. }
  300. h2 {
  301. font-size: calc( 1.5rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  302. line-height: calc( 1.3em + 0.2 * (100vw - 50rem) / (20 - 50) );
  303. }
  304. h3 {
  305. font-size: calc( 1.35rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  306. line-height: calc( 1.4em + 0.2 * (100vw - 50rem) / (20 - 50) );
  307. }
  308. }
  309. @media (min-width: 50em) {
  310. /* The right part of the addition *must* be a
  311. rem value. In this example we *could* change
  312. the whole declaration to font-size:2.5rem,
  313. but if our baseline value was not expressed
  314. in rem we would have to use calc. */
  315. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  316. font-size: calc( 1rem + .6 * 1rem );
  317. line-height: 1.5em;
  318. }
  319. p, li, pre, details {
  320. margin-left: 3rem;
  321. }
  322. h1 {
  323. font-size: calc( 1.9rem + 1.5 * 1rem );
  324. line-height: 1.2em;
  325. }
  326. h2 {
  327. font-size: calc( 1.5rem + 1.5 * 1rem );
  328. line-height: 1.3em;
  329. }
  330. h3 {
  331. font-size: calc( 1.35rem + 1.5 * 1rem );
  332. line-height: 1.4em;
  333. }
  334. figure img {
  335. max-width: 500px;
  336. max-height: 500px;
  337. }
  338. }
  339. figure.unsquared {
  340. margin-bottom: 1.5rem;
  341. }
  342. figure.unsquared img {
  343. height: inherit;
  344. }
  345. @media print {
  346. body { font-size: 100%; }
  347. a:after { content: " (" attr(href) ")"; }
  348. a, a:link, a:visited, a:after {
  349. text-decoration: underline;
  350. text-shadow: none !important;
  351. background-image: none !important;
  352. background: white;
  353. color: black;
  354. }
  355. abbr[title] { border-bottom: 0; }
  356. abbr[title]:after { content: " (" attr(title) ")"; }
  357. img { page-break-inside: avoid; }
  358. @page { margin: 2cm .5cm; }
  359. h1, h2, h3 { page-break-after: avoid; }
  360. p3 { orphans: 3; widows: 3; }
  361. img {
  362. max-width: 250px !important;
  363. max-height: 250px !important;
  364. }
  365. nav, aside { display: none; }
  366. }
  367. ul.with_columns {
  368. column-count: 1;
  369. }
  370. @media (min-width: 20em) {
  371. ul.with_columns {
  372. column-count: 2;
  373. }
  374. }
  375. @media (min-width: 50em) {
  376. ul.with_columns {
  377. column-count: 3;
  378. }
  379. }
  380. ul.with_two_columns {
  381. column-count: 1;
  382. }
  383. @media (min-width: 20em) {
  384. ul.with_two_columns {
  385. column-count: 1;
  386. }
  387. }
  388. @media (min-width: 50em) {
  389. ul.with_two_columns {
  390. column-count: 2;
  391. }
  392. }
  393. .gallery {
  394. display: flex;
  395. flex-wrap: wrap;
  396. justify-content: space-around;
  397. }
  398. .gallery figure img {
  399. margin-left: 1rem;
  400. margin-right: 1rem;
  401. }
  402. .gallery figure figcaption {
  403. font-family: 'ConcourseT3'
  404. }
  405. footer {
  406. font-family: 'ConcourseT3';
  407. display: flex;
  408. flex-direction: column;
  409. border-top: 3px solid white;
  410. padding: 4rem 0;
  411. background-color: #07486c;
  412. color: white;
  413. }
  414. footer > * {
  415. max-width: 50rem;
  416. margin: 0 auto;
  417. }
  418. footer a {
  419. color: #f1c40f;
  420. }
  421. footer .avatar {
  422. width: 200px;
  423. height: 200px;
  424. border-radius: 50%;
  425. float: left;
  426. -webkit-shape-outside: circle();
  427. shape-outside: circle();
  428. margin-right: 2rem;
  429. padding: 2px 5px 5px 2px;
  430. background: white;
  431. border-left: 1px solid #f1c40f;
  432. border-top: 1px solid #f1c40f;
  433. border-right: 5px solid #f1c40f;
  434. border-bottom: 5px solid #f1c40f;
  435. }
  436. </style>
  437. <h1>
  438. <span><a id="jumper" href="#jumpto" title="Un peu perdu ?">?</a></span>
  439. What Makes Software Good? (archive)
  440. <time>Pour la pérennité des contenus liés. Non-indexé, retrait sur simple email.</time>
  441. </h1>
  442. <section>
  443. <article>
  444. <h3><a href="https://medium.com/@mbostock/what-makes-software-good-943557f8a488">Source originale du contenu</a></h3>
  445. <p name="97e5" id="97e5" class="graf--p graf-after--h3">As someone who creates open-source software, I spend a lot of time thinking about how to make software better.</p>
  446. <p name="faf8" id="faf8" class="graf--p graf-after--p">This is unavoidable: there’s an unending stream of pleas for help on Stack Overflow, in GitHub issues and Slack mentions, in emails and direct messages. Fortunately, you also see people succeed and make fantastic things beyond your imagination, and knowing you helped is a powerful motivation to keep at it.</p>
  447. <p name="4691" id="4691" class="graf--p graf-after--p">So you wonder: what qualities of software lead people to succeed or fail? How can I improve my software and empower more people to be successful? Can I articulate any guiding principles, or do I just have intuition that I apply on a case-by-case basis? (Thinking about something and externalizing — articulating — that thought are two very different activities.) Perhaps something like <a href="https://www.vitsoe.com/us/about/good-design" data-href="https://www.vitsoe.com/us/about/good-design" class="markup--anchor markup--p-anchor" rel="nofollow">Dieter Ram’s principles</a> for good design, tailored for software?</p>
  448. <blockquote name="b9a5" id="b9a5" class="graf--pullquote pullquote graf-after--p">Good design is innovative.<br>Good design makes a product useful.<br>Good design is aesthetic.<br>Good design makes a product understandable.<br>Good design is unobtrusive.<br>Good design is honest.<br>Good design is long-lasting.<br>Good design is thorough down to the last detail.<br>Good design is environmentally-friendly.<br>Good design is as little design as possible.</blockquote>
  449. <p name="8bce" id="8bce" class="graf--p graf-after--pullquote">I’ve tried in the past to talk about <a href="https://vimeo.com/106198518" data-href="https://vimeo.com/106198518" class="markup--anchor markup--p-anchor" rel="nofollow">big picture stuff</a>. Things like finding the smallest interesting problem, identifying and minimizing harmful biases in tools, or leveraging related technologies and standards.</p>
  450. <p name="5720" id="5720" class="graf--p graf-after--p">The big picture is important — probably <em class="markup--em markup--p-em">more</em> important than what I’m writing about today — but I can’t help but feel that big picture advice is sometimes impractical or impossible to apply. Or worse, truisms. Like saying, “Make it as simple as possible, but no simpler.” Well, <em class="markup--em markup--p-em">duh</em>. We all want things to be simpler. But we may not know what to sacrifice in order to achieve that goal.</p>
  451. <p name="d6a9" id="d6a9" class="graf--p graf-after--p">And even if you get the big picture right, there’s no guarantee your design will be successful. The execution of an idea matters as much as the idea itself. The devil is in the details.</p>
  452. <p name="12f3" id="12f3" class="graf--p graf-after--p">If I can’t offer actionable big picture advice, perhaps there’s lesser advice that would be useful. A practical inspiration is Green &amp; Petre, whose <a href="https://en.wikipedia.org/wiki/Cognitive_dimensions_of_notations" data-href="https://en.wikipedia.org/wiki/Cognitive_dimensions_of_notations" class="markup--anchor markup--p-anchor" rel="nofollow">“cognitive dimensions” framework</a> defines a set of “discussion tools” to “raise the level of discourse” about the usability of “information artifacts” such as code.</p>
  453. <blockquote name="8c9c" id="8c9c" class="graf--pullquote pullquote graf-after--p">Abstraction gradient<br>Closeness of mapping<br>Consistency<br>Diffuseness<br>Error-proneness<br>Hard mental operations<br>Hidden dependencies<br>Premature commitment<br>Progressive evaluation<br>Role-expressiveness<br>Secondary notation<br>Viscosity<br>Visibility</blockquote>
  454. <p name="d00f" id="d00f" class="graf--p graf-after--pullquote">It’s not perfect; no framework is. It was conceived to study visual programming environments, and sometimes feels specific to that application. (Consider <em class="markup--em markup--p-em">visibility</em>, which refers to seeing all the code simultaneously. Is any software today small enough to be visible in its entirety on a single screen? Perhaps <em class="markup--em markup--p-em">modularity</em> would be better?) I find it difficult to assign some usability problems to one dimension or another. (Both <em class="markup--em markup--p-em">hidden dependencies</em> and <em class="markup--em markup--p-em">role-expressiveness</em> suggest I thought the code would do something other than what it did.) Still, it’s a good starting point for thinking about the “cognitive consequences” of software design.</p>
  455. <p name="c2eb" id="c2eb" class="graf--p graf-after--p">I won’t be defining a general framework. But I do have some observations I’d like to share, and this is as good a time as any to perform a <em class="markup--em markup--p-em">post hoc</em> rationalization of the last year or so I’ve spent on <a href="https://github.com/mbostock/d3/tree/4" data-href="https://github.com/mbostock/d3/tree/4" class="markup--anchor markup--p-anchor" rel="nofollow">D3 4.0</a>.</p>
  456. <p name="8b47" id="8b47" class="graf--p graf-after--p">I’m not revisiting the “big picture” design of D3. I’m quite happy with concepts like the <a href="https://github.com/d3/d3-selection" data-href="https://github.com/d3/d3-selection" class="markup--anchor markup--p-anchor" rel="nofollow">data join</a>, <a href="https://github.com/d3/d3-scale" data-href="https://github.com/d3/d3-scale" class="markup--anchor markup--p-anchor" rel="nofollow">scales</a>, and <a href="https://github.com/d3/d3-hierarchy" data-href="https://github.com/d3/d3-hierarchy" class="markup--anchor markup--p-anchor" rel="nofollow">layouts</a> decoupled from visual representation. There’s interesting research here, of course, but it hasn’t been my recent focus.</p>
  457. <p name="f9df" id="f9df" class="graf--p graf-after--p">I’m breaking D3 into modules — to make it usable in more applications, easier for others to extend, and more fun to develop — but I’m also identifying and fixing a surprising number of quirks and flaws in the API. Stuff that’s easily overlooked, but that I believe causes real pain and limits what people can do.</p>
  458. <p name="75eb" id="75eb" class="graf--p graf-after--p">I worry sometimes that the changes are trivial, especially when taken individually. I hope to convince you that they are not. I worry because I think we (that is, people who write software) tend to undervalue the usability of programming interfaces, instead considering more objective qualities that are easier to measure: <em class="markup--em markup--p-em">functionality</em>, <em class="markup--em markup--p-em">performance</em>, <em class="markup--em markup--p-em">correctness</em>.</p>
  459. <p name="3032" id="3032" class="graf--p graf-after--p">Those qualities matter, but poor usability has a real cost. Just ask anyone who has struggled to decipher a confusing block of code, or pulled their hair out fighting the debugger. We need to get better at evaluating usability sooner, and better at making software usable in the first place.</p>
  460. <p></div><div class="section-inner sectionLayout--outsetRow" data-paragraph-count="2"><figure name="792a" id="792a" class="graf--figure graf--layoutOutsetRow is-partialWidth graf-after--p" style="width: 50%;"><div class="aspectRatioPlaceholder is-locked"><div class="aspectRatioPlaceholder-fill" style="padding-bottom: 66.7%;"></div><div class="progressiveMedia js-progressiveMedia graf-image" data-image-id="1*5p5we6kQBUnJS7MXqzdDYg.jpeg" data-width="960" data-height="640" data-action="zoom" data-action-value="1*5p5we6kQBUnJS7MXqzdDYg.jpeg"><img src="https://cdn-images-1.medium.com/freeze/max/40/1*5p5we6kQBUnJS7MXqzdDYg.jpeg?q=20" crossorigin="anonymous" class="progressiveMedia-thumbnail js-progressiveMedia-thumbnail"><canvas class="progressiveMedia-canvas js-progressiveMedia-canvas"></canvas><img class="progressiveMedia-image js-progressiveMedia-image" data-src="https://cdn-images-1.medium.com/max/800/1*5p5we6kQBUnJS7MXqzdDYg.jpeg"><noscript class="js-progressiveMedia-inner"><img class="progressiveMedia-noscript js-progressiveMedia-inner" src="https://cdn-images-1.medium.com/max/800/1*5p5we6kQBUnJS7MXqzdDYg.jpeg"></noscript></div></div></figure><figure name="7d88" id="7d88" class="graf--figure graf--layoutOutsetRowContinue is-partialWidth graf-after--figure" style="width: 50%;"><div class="aspectRatioPlaceholder is-locked"><div class="aspectRatioPlaceholder-fill" style="padding-bottom: 66.7%;"></div><div class="progressiveMedia js-progressiveMedia graf-image" data-image-id="1*43y1tWj2XK_pM49LGR8yuQ.jpeg" data-width="960" data-height="640" data-action="zoom" data-action-value="1*43y1tWj2XK_pM49LGR8yuQ.jpeg"><img src="https://cdn-images-1.medium.com/freeze/max/40/1*43y1tWj2XK_pM49LGR8yuQ.jpeg?q=20" crossorigin="anonymous" class="progressiveMedia-thumbnail js-progressiveMedia-thumbnail"><canvas class="progressiveMedia-canvas js-progressiveMedia-canvas"></canvas><img class="progressiveMedia-image js-progressiveMedia-image" data-src="https://cdn-images-1.medium.com/max/800/1*43y1tWj2XK_pM49LGR8yuQ.jpeg"><noscript class="js-progressiveMedia-inner"><img class="progressiveMedia-noscript js-progressiveMedia-inner" src="https://cdn-images-1.medium.com/max/800/1*43y1tWj2XK_pM49LGR8yuQ.jpeg"></noscript></div></div><figcaption class="imageCaption" style="width: 200%; left: -100%;"><a href="https://www.morimasahiro-ds.org/open-archives/" data-href="https://www.morimasahiro-ds.org/open-archives/" class="markup--anchor markup--figure-anchor" rel="nofollow">Mori Masahiro Design Studio, LLC.</a>, CC-BY-3.0</figcaption></figure></div><div class="section-inner layoutSingleColumn"><p name="9e09" id="9e09" class="graf--p graf-after--figure">You can’t pick up a piece of code and feel its weight or texture in your hands. Code is an “information artifact” rather than a physical or graphical one. You interact with APIs through the manipulation of text in an editor or on the command line.</p><p name="30d9" id="30d9" class="graf--p graf-after--p">Yet this is interaction by the standard definition, subject to the complexities of human factors. So we should evaluate code, like any tool, not merely on whether it performs its intended task, but whether it is easy to become proficient, and whether using it is efficient and enjoyable. We should consider the affordances and even the aesthetics of code. Is it understandable? Is it frustrating? Is it beautiful?</p><p name="a0d5" id="a0d5" class="graf--p graf-after--p"><strong class="markup--strong markup--p-strong">Programming interfaces are <em class="markup--em markup--p-em">user</em> interfaces</strong>. Or, to put it another way: <strong class="markup--strong markup--p-strong">Programmers are people, too.</strong> On the subject of undervaluing the human aspect of design, again hear Rams:</p><blockquote name="545a" id="545a" class="graf--pullquote pullquote graf--startsWithDoubleQuote graf-after--p">“Indifference towards people and the reality in which they live is actually the one and only cardinal sin in design.”</blockquote><p name="9013" id="9013" class="graf--p graf-after--pullquote graf--last">This implies, for one, that good documentation does not excuse bad design. You can ask people to <a href="https://en.wikipedia.org/wiki/RTFM" data-href="https://en.wikipedia.org/wiki/RTFM" class="markup--anchor markup--p-anchor" rel="nofollow">RTFM</a>, but it is folly to assume they have read everything and memorized every detail. The clarity of examples, and the software’s decipherability and debuggability in the real world, are likely far more important. Form must communicate function.</p></div></div></section><section name="2736" class=" section--body"><div class="section-divider layoutSingleColumn"><hr class="section-divider"></div><div class="section-content"><div class="section-inner layoutSingleColumn"><p name="7258" id="7258" class="graf--p graf--first">With that preamble, here are some of the changes I’m making to D3 with an eye towards usability. But first a crash course on D3’s data-join.</p><h4 name="708a" id="708a" class="graf--h4 graf-after--p">Case 1. Removing the magic of <em class="markup--em markup--h4-em">enter</em>.append.</h4><p name="ccf2" id="ccf2" class="graf--p graf--startsWithDoubleQuote graf-after--h4">“D3” stands for <em class="markup--em markup--p-em">D</em>ata-<em class="markup--em markup--p-em">D</em>riven <em class="markup--em markup--p-em">D</em>ocuments. The <em class="markup--em markup--p-em">data</em> refers to the thing you want to visualize, and the <em class="markup--em markup--p-em">document</em> refers to its visual representation. It’s called a “document” because D3 is based on the standard model for web pages: the <a href="https://www.w3.org/DOM/" data-href="https://www.w3.org/DOM/" class="markup--anchor markup--p-anchor" rel="nofollow">Document Object Model</a>.</p><p name="7a83" id="7a83" class="graf--p graf-after--p">A simple page might look like this:</p><pre name="9b60" id="9b60" class="graf--pre graf-after--p">&lt;!DOCTYPE html&gt;<br>&lt;svg width=&quot;960&quot; height=&quot;540&quot;&gt;<br> &lt;g transform=&quot;translate(32,270)&quot;&gt;<br> &lt;text x=&quot;0&quot;&gt;b&lt;/text&gt;<br> &lt;text x=&quot;32&quot;&gt;c&lt;/text&gt;<br> &lt;text x=&quot;64&quot;&gt;d&lt;/text&gt;<br> &lt;text x=&quot;96&quot;&gt;k&lt;/text&gt;<br> &lt;text x=&quot;128&quot;&gt;n&lt;/text&gt;<br> &lt;text x=&quot;160&quot;&gt;r&lt;/text&gt;<br> &lt;text x=&quot;192&quot;&gt;t&lt;/text&gt;<br> &lt;/g&gt;<br>&lt;/svg&gt;</pre><p name="0387" id="0387" class="graf--p graf-after--pre">This happens to be an <a href="https://www.w3.org/html/" data-href="https://www.w3.org/html/" class="markup--anchor markup--p-anchor" rel="nofollow">HTML document</a> containing an <a href="https://www.w3.org/Graphics/SVG/" data-href="https://www.w3.org/Graphics/SVG/" class="markup--anchor markup--p-anchor" rel="nofollow">SVG element</a>, but you don’t need to know the semantics of every element and attribute to grasp the concept. Just know that each element, such as &lt;text&gt;…&lt;/text&gt; for a piece of text, is a discrete graphical mark. Elements are grouped hierarchically (the &lt;svg&gt; contains the &lt;g&gt;, which contains the &lt;text&gt;, and so on) so that you can position and style groups of elements.</p><p name="9d0a" id="9d0a" class="graf--p graf-after--p">A corresponding simple dataset might look like this:</p><pre name="4499" id="4499" class="graf--pre graf-after--p">var data = [<br> &quot;b&quot;,<br> &quot;c&quot;,<br> &quot;d&quot;,<br> &quot;k&quot;,<br> &quot;n&quot;,<br> &quot;r&quot;,<br> &quot;t&quot;<br>];</pre><p name="dd40" id="dd40" class="graf--p graf-after--pre">This dataset is an array of strings. (A string is a character sequence, though the strings here are individual letters.) But data can have any structure you want, if you can represent it in JavaScript.</p><p name="7df6" id="7df6" class="graf--p graf-after--p">For each entry (each string) in the data array, we need a corresponding &lt;text&gt; element in the document. This is the purpose of the data-join: a concise method for transforming a document — adding, removing, or modifying elements — so that it corresponds to data.</p><figure name="d8a9" id="d8a9" class="graf--figure graf-after--p"><div class="aspectRatioPlaceholder is-locked" style="max-width: 662px; max-height: 477px;"><div class="aspectRatioPlaceholder-fill" style="padding-bottom: 72.1%;"></div><div class="progressiveMedia js-progressiveMedia graf-image" data-image-id="1*E-3Nd1KJbfL4fiZ92N-DHg.png" data-width="662" data-height="477"><img src="https://cdn-images-1.medium.com/freeze/max/40/1*E-3Nd1KJbfL4fiZ92N-DHg.png?q=20" crossorigin="anonymous" class="progressiveMedia-thumbnail js-progressiveMedia-thumbnail"><canvas class="progressiveMedia-canvas js-progressiveMedia-canvas"></canvas><img class="progressiveMedia-image js-progressiveMedia-image" data-src="https://cdn-images-1.medium.com/max/1067/1*E-3Nd1KJbfL4fiZ92N-DHg.png"><noscript class="js-progressiveMedia-inner"><img class="progressiveMedia-noscript js-progressiveMedia-inner" src="https://cdn-images-1.medium.com/max/1067/1*E-3Nd1KJbfL4fiZ92N-DHg.png"></noscript></div></div></figure><p name="6c73" id="6c73" class="graf--p graf-after--figure">The data-join takes as input an array of data and an array of document elements, and returns three selections:</p><ul class="postList"><li name="3f60" id="3f60" class="graf--li graf-after--p">The <em class="markup--em markup--li-em">enter</em> selection represents “missing” elements (incoming data) that you may need to create and add to the document.</li><li name="0b44" id="0b44" class="graf--li graf-after--li">The <em class="markup--em markup--li-em">update</em> selection represents existing elements (persisting data) that you may need to modify (for example, repositioning).</li><li name="e763" id="e763" class="graf--li graf-after--li">The <em class="markup--em markup--li-em">exit</em> selection represents “leftover” elements (outgoing data) that you may need to remove from the document.</li></ul><p name="1a9b" id="1a9b" class="graf--p graf-after--li">The data-join doesn’t modify the document itself. It computes <em class="markup--em markup--p-em">enter</em>, <em class="markup--em markup--p-em">update</em> and <em class="markup--em markup--p-em">exit</em>, and then you apply the desired operations to each. That affords expressiveness: for example, to animate elements as they enter and exit.</p><figure name="8ed1" id="8ed1" class="graf--figure graf-after--p"><div class="aspectRatioPlaceholder is-locked" style="max-width: 700px; max-height: 100px;"><div class="aspectRatioPlaceholder-fill" style="padding-bottom: 14.299999999999999%;"></div><a href="http://bl.ocks.org/mbostock/a8a5baa4c4a470cda598" data-href="http://bl.ocks.org/mbostock/a8a5baa4c4a470cda598" class="graf-imageAnchor"><div class="progressiveMedia js-progressiveMedia graf-image" data-image-id="1*d9HkJCZ4mB4prMHnmU6fFQ.png" data-width="960" data-height="137"><img src="https://cdn-images-1.medium.com/freeze/max/40/1*d9HkJCZ4mB4prMHnmU6fFQ.png?q=20" crossorigin="anonymous" class="progressiveMedia-thumbnail js-progressiveMedia-thumbnail"><canvas class="progressiveMedia-canvas js-progressiveMedia-canvas"></canvas><img class="progressiveMedia-image js-progressiveMedia-image" data-src="https://cdn-images-1.medium.com/max/1067/1*d9HkJCZ4mB4prMHnmU6fFQ.png"><noscript class="js-progressiveMedia-inner"><img class="progressiveMedia-noscript js-progressiveMedia-inner" src="https://cdn-images-1.medium.com/max/1067/1*d9HkJCZ4mB4prMHnmU6fFQ.png"></noscript></div></a></div><figcaption class="imageCaption"><a href="http://bl.ocks.org/mbostock/a8a5baa4c4a470cda598" data-href="http://bl.ocks.org/mbostock/a8a5baa4c4a470cda598" class="markup--anchor markup--figure-anchor" rel="nofollow">bl.ocks.org/a8a5baa4c4a470cda598</a></figcaption></figure><p name="e023" id="e023" class="graf--p graf-after--figure">As you can imagine, the data-join is something you use often — when first creating a visualization, and again whenever the data changes. The usability of this feature is hugely important to D3’s overall usefulness. It looks like this:</p><pre name="3908" id="3908" class="graf--pre graf-after--p">var text = g<br> .selectAll(&quot;text&quot;)<br> .data(data, key); // JOIN</pre><pre name="ae2f" id="ae2f" class="graf--pre graf-after--pre">text.exit() // EXIT<br> .remove();</pre><pre name="6cc2" id="6cc2" class="graf--pre graf-after--pre">text // UPDATE<br> .attr(&quot;x&quot;, function(d, i) { return i * 32; });</pre><pre name="914c" id="914c" class="graf--pre graf-after--pre">text.enter() // ENTER<br> .append(&quot;text&quot;)<br> .attr(&quot;x&quot;, function(d, i) { return i * 32; }) // 🌶<br> .text(function(d) { return d; });</pre><p name="27b9" id="27b9" class="graf--p graf-after--pre">I’m glossing over a few details (like the <em class="markup--em markup--p-em">key</em> function that assigns data to elements), but I hope the gist is conveyed. After joining to data, the code above removes exiting elements, repositions updating elements, and appends entering elements.</p><p name="a6e0" id="a6e0" class="graf--p graf-after--p">There’s an irksome usability problem in the above code, which I’ve marked with a hot pepper 🌶. It is duplicate code: setting the <em class="markup--em markup--p-em">x</em> attribute on <em class="markup--em markup--p-em">enter</em> and <em class="markup--em markup--p-em">update</em>.</p><p name="9bcd" id="9bcd" class="graf--p graf-after--p">It’s common to apply operations to both entering and updating elements. If an element is updating (<em class="markup--em markup--p-em">i.e.</em>, you’re not creating it from scratch), you may need to modify it to reflect the new data. Those modifications often also apply to entering elements, since they must reflect the new data, too.</p><p name="3df1" id="3df1" class="graf--p graf-after--p">D3 2.0 introduced a change to address this duplication: appending to the enter selection would now copy entering elements into the update selection. Thus, any operations applied to the update selection <em class="markup--em markup--p-em">after</em> appending to the enter selection would apply to both entering <em class="markup--em markup--p-em">and</em> updating elements, and duplicate code could be eliminated:</p><pre name="3b80" id="3b80" class="graf--pre graf-after--p">var text = g<br> .selectAll(&quot;text&quot;)<br> .data(data, key); // JOIN</pre><pre name="e3bc" id="e3bc" class="graf--pre graf-after--pre">text.exit() // EXIT<br> .remove();</pre><pre name="3cb8" id="3cb8" class="graf--pre graf-after--pre">text.enter() // ENTER<br> .append(&quot;text&quot;) // 🌶<br> .text(function(d) { return d; });</pre><pre name="5e7c" id="5e7c" class="graf--pre graf-after--pre">text // ENTER + UPDATE<br> .attr(&quot;x&quot;, function(d, i) { return i * 32; });</pre><p name="f2a9" id="f2a9" class="graf--p graf-after--pre">Alas, this made usability <em class="markup--em markup--p-em">worse</em>.</p><p name="e90f" id="e90f" class="graf--p graf-after--p">First, there’s no indication of what’s happening under the hood (poor <em class="markup--em markup--p-em">role-expressiveness</em>, or perhaps a <em class="markup--em markup--p-em">hidden dependency</em>). Most of the time, <em class="markup--em markup--p-em">selection</em>.append creates, appends and selects new elements; it does that here, but it <em class="markup--em markup--p-em">also</em> silently modifies the update selection. Surprise!</p><p name="ba6f" id="ba6f" class="graf--p graf-after--p">Second, the code is now dependent on the order of operations: if the operations to the update selection are applied before <em class="markup--em markup--p-em">enter</em>.append, they only affect updating nodes; if they occur after, they affect both entering and updating. The goal of the data-join is to eliminate such intricate logic, and to enable a more declarative specification of document transformations without complicated branching and iteration. The code might look simple, but it’s brushed the complexity under the rug.</p><p name="79ab" id="79ab" class="graf--p graf-after--p">D3 4.0 removes the magic of <em class="markup--em markup--p-em">enter</em>.append. (In fact, D3 4.0 removes the distinction between <em class="markup--em markup--p-em">enter</em> and normal selections entirely: there is now only one class of selection.) In its place, a new <em class="markup--em markup--p-em">selection</em>.merge method can unify the <em class="markup--em markup--p-em">enter</em> and <em class="markup--em markup--p-em">update</em> selections:</p><pre name="5687" id="5687" class="graf--pre graf-after--p">var text = g<br> .selectAll(&quot;text&quot;)<br> .data(data, key); // JOIN</pre><pre name="8a7c" id="8a7c" class="graf--pre graf-after--pre">text.exit() // EXIT<br> .remove();</pre><pre name="a75f" id="a75f" class="graf--pre graf-after--pre">text.enter() // ENTER<br> .append(&quot;text&quot;)<br> .text(function(d) { return d; })<br> .merge(text) // ENTER + UPDATE<br> .attr(&quot;x&quot;, function(d, i) { return i * 32; });</pre><p name="56d7" id="56d7" class="graf--p graf-after--pre">This eliminates the duplicate code without corrupting the behavior of a common method (<em class="markup--em markup--p-em">selection</em>.append) and without introducing a subtle dependency on ordering. Furthermore, the <em class="markup--em markup--p-em">selection</em>.merge method serves as a signpost to unfamiliar readers, which they can look up in the documentation.</p><h4 name="57ff" id="57ff" class="graf--h4 graf-after--p">Maxim 1. Avoid overloading meaning.</h4><p name="d7fc" id="d7fc" class="graf--p graf-after--h4">What can we learn from this failure? D3 3.x violated a Rams principle: good design makes a product understandable. In cognitive dimension terms, it had poor <em class="markup--em markup--p-em">consistency</em> because <em class="markup--em markup--p-em">selection</em>.append behaved differently on enter selections, and thus the user can’t extend understanding of normal selections to enter. It had poor <em class="markup--em markup--p-em">role-expressiveness</em> because the latter behavior wasn’t obvious. And there’s a <em class="markup--em markup--p-em">hidden dependency: </em>operations on the <em class="markup--em markup--p-em">text</em> selection must be run after appending to enter, though nothing in the code makes this requirement apparent.</p><p name="8000" id="8000" class="graf--p graf-after--p graf--last">D3 4.0 avoids overloading meaning. Rather than silently adding functionality to <em class="markup--em markup--p-em">enter</em>.append — even if it is useful in a common case — <em class="markup--em markup--p-em">selection</em>.append always only appends elements. If you want to merge selections, you need a new method! Hence, <em class="markup--em markup--p-em">selection</em>.merge.</p></div></div></section><section name="f84b" class=" section--body"><div class="section-divider layoutSingleColumn"><hr class="section-divider"></div><div class="section-content"><div class="section-inner layoutSingleColumn"><h4 name="4862" id="4862" class="graf--h4 graf--first">Case 2. Removing the magic of <em class="markup--em markup--h4-em">transition</em>.each.</h4><p name="9536" id="9536" class="graf--p graf-after--h4">A <a href="https://github.com/d3/d3-transition" data-href="https://github.com/d3/d3-transition" class="markup--anchor markup--p-anchor" rel="nofollow">transition</a> is a selection-like interface for animating changes to the document. Instead of changing the document instantaneously, transitions smoothly interpolate the document from its current state to the desired target state over a given duration.</p><p name="7204" id="7204" class="graf--p graf-after--p">Transitions can be heterogeneous: sometimes you want to synchronize a transition across multiple selections. For example, to transition an axis, you must reposition tick lines and labels simultaneously:</p><figure name="264b" id="264b" class="graf--figure graf-after--p"><div class="aspectRatioPlaceholder is-locked" style="max-width: 700px; max-height: 347px;"><div class="aspectRatioPlaceholder-fill" style="padding-bottom: 49.5%;"></div><a href="https://bl.ocks.org/mbostock/1166403" data-href="https://bl.ocks.org/mbostock/1166403" class="graf-imageAnchor"><div class="progressiveMedia js-progressiveMedia graf-image" data-image-id="1*jpTyvqM4Xbgl1mNNx_uwDw.png" data-width="949" data-height="470"><img src="https://cdn-images-1.medium.com/freeze/max/40/1*jpTyvqM4Xbgl1mNNx_uwDw.png?q=20" crossorigin="anonymous" class="progressiveMedia-thumbnail js-progressiveMedia-thumbnail"><canvas class="progressiveMedia-canvas js-progressiveMedia-canvas"></canvas><img class="progressiveMedia-image js-progressiveMedia-image" data-src="https://cdn-images-1.medium.com/max/1067/1*jpTyvqM4Xbgl1mNNx_uwDw.png"><noscript class="js-progressiveMedia-inner"><img class="progressiveMedia-noscript js-progressiveMedia-inner" src="https://cdn-images-1.medium.com/max/1067/1*jpTyvqM4Xbgl1mNNx_uwDw.png"></noscript></div></a></div><figcaption class="imageCaption"><a href="https://bl.ocks.org/mbostock/1166403" data-href="https://bl.ocks.org/mbostock/1166403" class="markup--anchor markup--figure-anchor" rel="nofollow">bl.ocks.org/1166403</a></figcaption></figure><p name="5dfc" id="5dfc" class="graf--p graf-after--figure">One way of specifying such a transition:</p><pre name="58e6" id="58e6" class="graf--pre graf-after--p">d3.selectAll(&quot;line&quot;).transition()<br> .duration(750)<br> .attr(&quot;x1&quot;, x)<br> .attr(&quot;x2&quot;, x);</pre><pre name="a610" id="a610" class="graf--pre graf-after--pre">d3.selectAll(&quot;text&quot;).transition() // 🌶<br> .duration(750) // 🌶<br> .attr(&quot;x&quot;, x);</pre><p name="ab5b" id="ab5b" class="graf--p graf-after--pre">(Here <em class="markup--em markup--p-em">x</em> is a function, such as a <a href="https://github.com/d3/d3-scale" data-href="https://github.com/d3/d3-scale" class="markup--anchor markup--p-anchor" rel="nofollow">linear scale</a>, to compute the horizontal position of each tick from its corresponding data value.)</p><p name="69da" id="69da" class="graf--p graf-after--p">The two peppers suggest caution. Again there’s duplicate code: the transitions for the line and text elements are created independently, so we must repeat the timing parameters such as delay and duration.</p><p name="8f98" id="8f98" class="graf--p graf-after--p">A more subtle problem is that there’s no guarantee the two transitions are synchronized! The second transition is created after the first, so its start time is slightly later. A difference of a millisecond or two may not be visible here, but it might in other applications.</p><p name="337d" id="337d" class="graf--p graf-after--p">D3 2.8 introduced a new feature to synchronize heterogenous transitions such as these: it added magic to <em class="markup--em markup--p-em">transition</em>.each — a method for iterating over each selected element — such that any new transition created within the callback would inherit timing from the surrounding transition. So now you could say:</p><pre name="b173" id="b173" class="graf--pre graf-after--p">var t = d3.transition()<br> .duration(750);</pre><pre name="7ff1" id="7ff1" class="graf--pre graf-after--pre">t.each(function() {<br> d3.selectAll(&quot;line&quot;).transition() // 🌶<br> .attr(&quot;x1&quot;, x)<br> .attr(&quot;x2&quot;, x);</pre><pre name="2b64" id="2b64" class="graf--pre graf-after--pre"> d3.selectAll(&quot;text&quot;).transition() // 🌶<br> .attr(&quot;x&quot;, x);<br>});</pre><p name="3df4" id="3df4" class="graf--p graf-after--pre">Like <em class="markup--em markup--p-em">enter</em>.append, this has poor usability: it changes the behavior of existing methods (<em class="markup--em markup--p-em">selection</em>.each and <em class="markup--em markup--p-em">selection</em>.transition) with no indication that the behavior has changed. If you create a second transition on a given selection, that new transition doesn’t pre-empt the old one; you’re just reselecting the old transition. Oops!</p><p name="e8c9" id="e8c9" class="graf--p graf-after--p">This example is regrettably contrived for the sake of pedagogy. There’s another, cleaner way (even in D3 3.x) to synchronize transitions across selections using <em class="markup--em markup--p-em">transition</em>.select and <em class="markup--em markup--p-em">transition</em>.selectAll:</p><pre name="d104" id="d104" class="graf--pre graf-after--p">var t = d3.transition()<br> .duration(750);</pre><pre name="de9a" id="de9a" class="graf--pre graf-after--pre">t.selectAll(&quot;line&quot;)<br> .attr(&quot;x1&quot;, x)<br> .attr(&quot;x2&quot;, x);</pre><pre name="c604" id="c604" class="graf--pre graf-after--pre">t.selectAll(&quot;text&quot;)<br> .attr(&quot;x&quot;, x);</pre><p name="e2ea" id="e2ea" class="graf--p graf-after--pre">Here, the transition <em class="markup--em markup--p-em">t</em> on the document root is applied to the line and text elements by selecting them. This is an elegant, but limited, solution: the transition can only be applied to a <em class="markup--em markup--p-em">new</em> selection, and not to an <em class="markup--em markup--p-em">existing</em> selection. Reselecting is always possible, but this is unnecessary work and unnecessary code (particularly for the transient <em class="markup--em markup--p-em">enter</em>, <em class="markup--em markup--p-em">update</em>, and <em class="markup--em markup--p-em">exit</em> selections returned by the data-join).</p><p name="6234" id="6234" class="graf--p graf-after--p">D3 4.0 removes the magic of <em class="markup--em markup--p-em">transition</em>.each; it now shares the implementation of <em class="markup--em markup--p-em">selection</em>.each. Instead, <em class="markup--em markup--p-em">selection</em>.transition can be passed a transition, causing the new transition to inherit timing from the specified transition. Now we can achieve the desired synchronization when creating new selections:</p><pre name="67dd" id="67dd" class="graf--pre graf-after--p">var t = d3.transition()<br> .duration(750);</pre><pre name="ad93" id="ad93" class="graf--pre graf-after--pre">d3.selectAll(&quot;line&quot;).transition(t)<br> .attr(&quot;x1&quot;, x)<br> .attr(&quot;x2&quot;, x);</pre><pre name="4864" id="4864" class="graf--pre graf-after--pre">d3.selectAll(&quot;text&quot;).transition(t)<br> .attr(&quot;x&quot;, x);</pre><p name="66b4" id="66b4" class="graf--p graf-after--pre">Or when using existing selections:</p><pre name="30ab" id="30ab" class="graf--pre graf-after--p">var t = d3.transition()<br> .duration(750);</pre><pre name="1aa1" id="1aa1" class="graf--pre graf-after--pre">line.transition(t)<br> .attr(&quot;x1&quot;, x)<br> .attr(&quot;x2&quot;, x);</pre><pre name="8a28" id="8a28" class="graf--pre graf-after--pre">text.transition(t)<br> .attr(&quot;x&quot;, x);</pre><p name="1beb" id="1beb" class="graf--p graf-after--pre">This new design arguably corrupts the behavior of <em class="markup--em markup--p-em">selection</em>.transition. But a new method signature (a method with the same name, but different arguments) is a fairly common design pattern, and at least the difference in behavior is localized to a single call and explicitly enabled at the call site.</p><h4 name="c6ce" id="c6ce" class="graf--h4 graf-after--p">Maxim 2. Avoid modal behavior.</h4><p name="52e6" id="52e6" class="graf--p graf-after--h4">This is an extension of the previous maxim, avoid overloading meaning, for a more egregious violation. Here, D3 2.8 introduced inconsistency with <em class="markup--em markup--p-em">selection</em>.transition, but the behavioral trigger was not a different class; it was simply being inside a call to <em class="markup--em markup--p-em">transition</em>.each. A remarkable consequence of this design is that you can change the behavior of code you didn’t write by wrapping it with <em class="markup--em markup--p-em">transition</em>.each!</p><p name="4102" id="4102" class="graf--p graf-after--p">If you see code that’s setting a global variable to trigger a global change in behavior, chances are it’s a bad idea.</p><p name="bb94" id="bb94" class="graf--p graf-after--p graf--last">In hindsight, this one is particularly glaring. <em class="markup--em markup--p-em">What was I thinking? Am I a failed designer?</em> There is consolation in understanding why bad ideas are attractive: it is easier to recognize and reject them in the future. Here, I recall trying to minimize <em class="markup--em markup--p-em">perceived</em> complexity by avoiding new methods. However, this is a clear example where introducing new methods (or signatures) is simpler than overloading existing ones.</p></div></div></section><section name="8b6a" class=" section--body"><div class="section-divider layoutSingleColumn"><hr class="section-divider"></div><div class="section-content"><div class="section-inner layoutSingleColumn"><h4 name="a1ca" id="a1ca" class="graf--h4 graf--first">Case 3. Removing the magic of d3.transition(selection).</h4><p name="c7d6" id="c7d6" class="graf--p graf-after--h4">A powerful concept in most modern programming languages is the ability to define reusable units of code as functions. By wrapping code in a function, you can call it wherever you want, without resorting to copy-and-paste. While some software libraries define their own abstractions for reusing code (say, extending a chart type), D3 is agnostic about how you encapsulate code, and I recommend just <a href="https://bost.ocks.org/mike/chart/" data-href="https://bost.ocks.org/mike/chart/" class="markup--anchor markup--p-anchor" rel="nofollow">using a function</a>.</p><p name="a636" id="a636" class="graf--p graf-after--p">Since selections and transitions share many methods, such as <em class="markup--em markup--p-em">selection</em>.style and <em class="markup--em markup--p-em">transition</em>.style for setting style properties, you can write a function that operates on either selections or transitions. For example:</p><pre name="b2a9" id="b2a9" class="graf--pre graf-after--p">function makeitred(context) {<br> context.style(&quot;color&quot;, &quot;red&quot;);<br>}</pre><p name="683f" id="683f" class="graf--p graf-after--pre">You can pass <em class="markup--em markup--p-em">makeitred</em> a selection to instantaneously set the body’s text color to red:</p><pre name="82e8" id="82e8" class="graf--pre graf-after--p">d3.select(&quot;body&quot;).call(makeitred);</pre><p name="d8f8" id="d8f8" class="graf--p graf-after--pre">But you can also pass <em class="markup--em markup--p-em">makeitred</em> a transition, in which case the text color will fade to red over a short duration:</p><pre name="440e" id="440e" class="graf--pre graf-after--p">d3.select(&quot;body&quot;).transition().call(makeitred);</pre><p name="16c8" id="16c8" class="graf--p graf-after--pre">This approach is taken by D3’s built-in components such as axes and brushes, and also by behaviors such as zoom.</p><p name="21e6" id="21e6" class="graf--p graf-after--p">A gotcha of this approach, however, is that transitions and selections do not have identical APIs, so not all code can be agnostic. Some operations, such as computing a data-join to update axis ticks, requires selections.</p><p name="c275" id="c275" class="graf--p graf-after--p">D3 2.8 introduced another misguided feature for this use case: it overloaded d3.transition, which normally returns a new transition on the document root. If you passed a selection to d3.transition, and you were inside a <em class="markup--em markup--p-em">transition</em>.each callback, then d3.transition would return a new transition on the specified selection; otherwise it would just return the specified selection. (This feature was added in the same commit as the <em class="markup--em markup--p-em">transition</em>.each flaw discussed above. When it rains, it pours!)</p><p name="7a7b" id="7a7b" class="graf--p graf-after--p">You should infer from my cumbersome description that this feature was a bad idea. But let’s take a closer look, for science. Here’s an equivalent way of writing the above <em class="markup--em markup--p-em">makeitred</em> function that allows some code (using <em class="markup--em markup--p-em">s</em>) to be restricted to the selection API with other code (using <em class="markup--em markup--p-em">t</em>) instead applied to the transition API if <em class="markup--em markup--p-em">context</em> is a transition:</p><pre name="5f9e" id="5f9e" class="graf--pre graf-after--p">function makeitred(context) {<br> context.each(function() { // 🌶<br> var s = d3.select(this),<br> t = d3.transition(s); // 🌶<br> t.style(&quot;color&quot;, &quot;red&quot;);<br> });<br>}</pre><p name="f11a" id="f11a" class="graf--p graf-after--pre">The <em class="markup--em markup--p-em">transition</em>.each magic is here, too: d3.transition calls <em class="markup--em markup--p-em">selection</em>.transition and is inside a <em class="markup--em markup--p-em">transition</em>.each callback, so the new transition inherits timing from the surrounding transition. There’s new confusion in that d3.transition doesn’t do what it normally does. And there’s confusion in that <em class="markup--em markup--p-em">context</em> and <em class="markup--em markup--p-em">t </em>are unknown types — either a selection or a transition — though perhaps that’s justified by the convenience of calling <em class="markup--em markup--p-em">makeitred</em> on either selections or transitions.</p><p name="c666" id="c666" class="graf--p graf-after--p">D3 4.0 removes d3.transition(<em class="markup--em markup--p-em">selection</em>); d3.transition can now only be used to create a transition on the document root, as with d3.selection. To disambiguate a selection from a transition, do what you normally do in JavaScript to check types: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof" data-href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof" class="markup--anchor markup--p-anchor" rel="nofollow">instanceof</a>, or if you prefer, <a href="https://en.wikipedia.org/wiki/Duck_typing" data-href="https://en.wikipedia.org/wiki/Duck_typing" class="markup--anchor markup--p-anchor" rel="nofollow">duck typing</a>.</p><pre name="b297" id="b297" class="graf--pre graf-after--p">function makeitred(context) {<br> var s = context.selection ? context.selection() : context,<br> t = context;<br> t.style(&quot;color&quot;, &quot;red&quot;);<br>}</pre><p name="aafe" id="aafe" class="graf--p graf-after--pre">Notice that in addition to removing the magic of <em class="markup--em markup--p-em">transition</em>.each and d3.transition, the new <em class="markup--em markup--p-em">makeitred</em> function avoids <em class="markup--em markup--p-em">transition</em>.each entirely, while still allowing you to write code that is selection-specific using D3 4.0’s new <em class="markup--em markup--p-em">transition</em>.selection method. This is a contrived example in that the selection <em class="markup--em markup--p-em">s</em> isn’t used, and <em class="markup--em markup--p-em">t</em> has the same value as <em class="markup--em markup--p-em">context</em>, and thus it can be trivially reduced back to the original definition:</p><pre name="c806" id="c806" class="graf--pre graf-after--p">function makeitred(context) {<br> context.style(&quot;color&quot;, &quot;red&quot;);<br>}</pre><p name="0215" id="0215" class="graf--p graf-after--pre">But that’s my point. The need for some selection-specific code should not require a complete rewrite to use <em class="markup--em markup--p-em">transition</em>.each! Green &amp; Petre call that <em class="markup--em markup--p-em">premature commitment</em>.</p><h4 name="8990" id="8990" class="graf--h4 graf-after--p">Maxim 3. Favor parsimony.</h4><p name="1ce1" id="1ce1" class="graf--p graf-after--h4 graf--last">The d3.transition method was trying to be clever and combine two operations. The first is checking whether you’re inside the magic <em class="markup--em markup--p-em">transition</em>.each callback. If you are, the second is deriving a new transition from a selection. Yet the latter is already possible using <em class="markup--em markup--p-em">selection</em>.transition, so d3.transition was trying to do too much and hiding too much as a result.</p></div></div></section><section name="52ad" class=" section--body"><div class="section-divider layoutSingleColumn"><hr class="section-divider"></div><div class="section-content"><div class="section-inner layoutSingleColumn"><h4 name="c225" id="c225" class="graf--h4 graf--first">Case 4. Repeating transitions with d3.active.</h4><p name="5464" id="5464" class="graf--p graf-after--h4">D3 transitions are finite sequences. Most often, a transition is just a single stage, transitioning from the current state of the document to the desired target state. However, sometimes you want more elaborate sequences that go through several stages:</p><figure name="fbd7" id="fbd7" class="graf--figure graf-after--p"><div class="aspectRatioPlaceholder is-locked" style="max-width: 700px; max-height: 365px;"><div class="aspectRatioPlaceholder-fill" style="padding-bottom: 52.1%;"></div><a href="http://bl.ocks.org/mbostock/4341417" data-href="http://bl.ocks.org/mbostock/4341417" class="graf-imageAnchor"><div class="progressiveMedia js-progressiveMedia graf-image" data-image-id="1*671Hu-AFUplHjy3t4qqcOQ.png" data-width="960" data-height="500"><img src="https://cdn-images-1.medium.com/freeze/max/40/1*671Hu-AFUplHjy3t4qqcOQ.png?q=20" crossorigin="anonymous" class="progressiveMedia-thumbnail js-progressiveMedia-thumbnail"><canvas class="progressiveMedia-canvas js-progressiveMedia-canvas"></canvas><img class="progressiveMedia-image js-progressiveMedia-image" data-src="https://cdn-images-1.medium.com/max/1067/1*671Hu-AFUplHjy3t4qqcOQ.png"><noscript class="js-progressiveMedia-inner"><img class="progressiveMedia-noscript js-progressiveMedia-inner" src="https://cdn-images-1.medium.com/max/1067/1*671Hu-AFUplHjy3t4qqcOQ.png"></noscript></div></a></div><figcaption class="imageCaption"><a href="http://bl.ocks.org/mbostock/4341417" data-href="http://bl.ocks.org/mbostock/4341417" class="markup--anchor markup--figure-anchor" rel="nofollow">bl.ocks.org/4341417</a></figcaption></figure><p name="486e" id="486e" class="graf--p graf-after--figure">(Use caution when staging animations! Read <a href="http://vis.berkeley.edu/papers/animated_transitions/" data-href="http://vis.berkeley.edu/papers/animated_transitions/" class="markup--anchor markup--p-anchor" rel="nofollow">Animated Transitions in Statistical Data Graphics</a> by Heer &amp; Robertson.)</p><p name="79a2" id="79a2" class="graf--p graf-after--p">Sometimes, you might even want to repeat a sequence indefinitely, as in this toy example of circles undulating back and forth:</p><figure name="c9ab" id="c9ab" class="graf--figure graf-after--p"><div class="aspectRatioPlaceholder is-locked" style="max-width: 700px; max-height: 365px;"><div class="aspectRatioPlaceholder-fill" style="padding-bottom: 52.1%;"></div><a href="http://bl.ocks.org/mbostock/346f4d967650b27c0511" data-href="http://bl.ocks.org/mbostock/346f4d967650b27c0511" class="graf-imageAnchor"><div class="progressiveMedia js-progressiveMedia graf-image" data-image-id="1*B2v7ll1xGYhqj_zSaVvxzA.png" data-width="960" data-height="500"><img src="https://cdn-images-1.medium.com/freeze/max/40/1*B2v7ll1xGYhqj_zSaVvxzA.png?q=20" crossorigin="anonymous" class="progressiveMedia-thumbnail js-progressiveMedia-thumbnail"><canvas class="progressiveMedia-canvas js-progressiveMedia-canvas"></canvas><img class="progressiveMedia-image js-progressiveMedia-image" data-src="https://cdn-images-1.medium.com/max/1067/1*B2v7ll1xGYhqj_zSaVvxzA.png"><noscript class="js-progressiveMedia-inner"><img class="progressiveMedia-noscript js-progressiveMedia-inner" src="https://cdn-images-1.medium.com/max/1067/1*B2v7ll1xGYhqj_zSaVvxzA.png"></noscript></div></a></div><figcaption class="imageCaption"><a href="http://bl.ocks.org/mbostock/346f4d967650b27c0511" data-href="http://bl.ocks.org/mbostock/346f4d967650b27c0511" class="markup--anchor markup--figure-anchor" rel="nofollow">bl.ocks.org/346f4d967650b27c0511</a></figcaption></figure><p name="a531" id="a531" class="graf--p graf-after--figure">D3 has no dedicated method for infinite transition sequences, but you can create a new transition when an old one ends by listening to transition <em class="markup--em markup--p-em">start</em> or <em class="markup--em markup--p-em">end</em> events. This led to the most confusing example code I’ve ever written:</p><pre name="df11" id="df11" class="graf--pre graf-after--p">svg.selectAll(&quot;circle&quot;)<br> .transition()<br> .duration(2500)<br> .delay(function(d) { return d * 40; })<br> .each(slide); // 🌶</pre><pre name="b816" id="b816" class="graf--pre graf-after--pre">function slide() {<br> var circle = d3.select(this);<br> (function repeat() {<br> circle = circle.transition() // 🌶<br> .attr(&quot;cx&quot;, width)<br> .transition()<br> .attr(&quot;cx&quot;, 0)<br> .each(&quot;end&quot;, repeat);<br> })(); // 🌶<br>}</pre><p name="43f8" id="43f8" class="graf--p graf-after--pre">Three peppers! I’m hesitant to even attempt an explanation after the “cognitive consequences” we’ve sustained from the earlier ones. But you’ve made it this far, so I’ll do my best.</p><p name="b137" id="b137" class="graf--p graf-after--p">First, <em class="markup--em markup--p-em">transition</em>.each invokes the <em class="markup--em markup--p-em">slide</em> callback, iterating over each circle. The <em class="markup--em markup--p-em">slide</em> callback defines a self-invoking <em class="markup--em markup--p-em">repeat</em> closure which captures the <em class="markup--em markup--p-em">circle</em> variable. Initially <em class="markup--em markup--p-em">circle</em> represents a selection of one circle element; the first stage of the transition is thus created using <em class="markup--em markup--p-em">selection</em>.transition, inheriting timing from the surrounding transition! The second stage is created using <em class="markup--em markup--p-em">transition</em>.transition so that it begins when the first stage ends. This second stage is then assigned to <em class="markup--em markup--p-em">circle</em>. Lastly, each time the two-stage transition sequence ends, <em class="markup--em markup--p-em">repeat</em> is called, repeating and redefining the <em class="markup--em markup--p-em">circle</em> transition.</p><p name="f57b" id="f57b" class="graf--p graf-after--p">Also, did you notice that <em class="markup--em markup--p-em">transition</em>.each with one argument does something completely different than <em class="markup--em markup--p-em">transition</em>.each with two arguments? I probably need a fourth pepper.</p><p name="55b6" id="55b6" class="graf--p graf-after--p">Phew!</p><p name="5bc6" id="5bc6" class="graf--p graf-after--p">Now let’s compare to D3 4.0:</p><pre name="3725" id="3725" class="graf--pre graf-after--p">svg.selectAll(&quot;circle&quot;)<br> .transition()<br> .duration(2500)<br> .delay(function(d) { return d * 40; })<br> .on(&quot;start&quot;, slide);</pre><pre name="af95" id="af95" class="graf--pre graf-after--pre">function slide() {<br> d3.active(this)<br> .attr(&quot;cx&quot;, width)<br> .transition()<br> .attr(&quot;cx&quot;, 0)<br> .transition()<br> .on(&quot;start&quot;, slide);<br>}</pre><p name="a73e" id="a73e" class="graf--p graf-after--pre">D3 4.0 introduces <a href="https://github.com/d3/d3-transition#active" data-href="https://github.com/d3/d3-transition#active" class="markup--anchor markup--p-anchor" rel="nofollow">d3.active</a>, which returns the active transition on the specified element. This eliminates the need to capture a local variable for each circle (the <em class="markup--em markup--p-em">circle</em> variable), and by extension the need for a self-invoking closure (the <em class="markup--em markup--p-em">repeat</em> function), and the need for <em class="markup--em markup--p-em">transition</em>.each magic!</p><h4 name="e870" id="e870" class="graf--h4 graf-after--p">Maxim 4. Obscure solutions are not solutions.</h4><p name="b7e7" id="b7e7" class="graf--p graf-after--h4">This is a case where there’s a valid way to solve a problem, but it’s so intricate and brittle that you’re unlikely to discover it and you’re never going to remember it. <em class="markup--em markup--p-em">I wrote the library</em> and I still had to Google it.</p><p name="a628" id="a628" class="graf--p graf-after--p graf--last">Also, it was ugly.</p></div></div></section><section name="4391" class=" section--body"><div class="section-divider layoutSingleColumn"><hr class="section-divider"></div><div class="section-content"><div class="section-inner layoutSingleColumn"><h4 name="9576" id="9576" class="graf--h4 graf--first">Case 5. Freezing time in the background.</h4><p name="5dcc" id="5dcc" class="graf--p graf-after--h4">An infinitely-repeating transition in D3 3.x exhibits interesting behavior if you leave it open in a background tab for a long time. Well, by “interesting” I mean it looks like this:</p><figure name="c2e2" id="c2e2" class="graf--figure graf-after--p"><div class="aspectRatioPlaceholder is-locked" style="max-width: 700px; max-height: 317px;"><div class="aspectRatioPlaceholder-fill" style="padding-bottom: 45.2%;"></div><div class="progressiveMedia js-progressiveMedia graf-image" data-image-id="1*hNeI6pxxCL3_i6PnWaJuPQ.png" data-width="1640" data-height="742" data-action="zoom" data-action-value="1*hNeI6pxxCL3_i6PnWaJuPQ.png"><img src="https://cdn-images-1.medium.com/freeze/max/40/1*hNeI6pxxCL3_i6PnWaJuPQ.png?q=20" crossorigin="anonymous" class="progressiveMedia-thumbnail js-progressiveMedia-thumbnail"><canvas class="progressiveMedia-canvas js-progressiveMedia-canvas"></canvas><img class="progressiveMedia-image js-progressiveMedia-image" data-src="https://cdn-images-1.medium.com/max/1067/1*hNeI6pxxCL3_i6PnWaJuPQ.png"><noscript class="js-progressiveMedia-inner"><img class="progressiveMedia-noscript js-progressiveMedia-inner" src="https://cdn-images-1.medium.com/max/1067/1*hNeI6pxxCL3_i6PnWaJuPQ.png"></noscript></div></div></figure><p name="5c60" id="5c60" class="graf--p graf-after--figure">This happens because when the tab is returned to the foreground, it diligently attempts to show all the transitions you missed. If a tab defines transitions on hundreds of elements per second when in the foreground, and is backgrounded for several hours, that could be millions of transitions!</p><p name="625f" id="625f" class="graf--p graf-after--p">Of course there’s no point in running these transitions: they are scheduled in the past and will end as soon as they start. But since an infinite chain of transitions never interrupts itself, the transitions must proceed.</p><p name="9789" id="9789" class="graf--p graf-after--p">D3 4.0 fixes this problem by changing the definition of time. Transitions don’t typically need to be synchronized with absolute time; transitions are primarily perceptual aids for tracking objects across views. D3 4.0 therefore runs on <em class="markup--em markup--p-em">perceived time</em>, which only advances when the page is in the foreground. When a tab is backgrounded and returned to the foreground, it simply picks up as if nothing had happened.</p><h4 name="054e" id="054e" class="graf--h4 graf-after--p">Maxim 5. Question your assumptions.</h4><p name="019c" id="019c" class="graf--p graf-after--h4 graf--last">Sometimes a design flaw may not be fixable by adding or changing a single method. Instead, there may be an underlying assumption that needs reexamination — like that time is absolute.</p></div></div></section><section name="4c1a" class=" section--body"><div class="section-divider layoutSingleColumn"><hr class="section-divider"></div><div class="section-content"><div class="section-inner layoutSingleColumn"><h4 name="16fe" id="16fe" class="graf--h4 graf--first">Case 6. Cancelling transitions with <em class="markup--em markup--h4-em">selection</em>.interrupt.</h4><p name="ca7d" id="ca7d" class="graf--p graf-after--h4">Transitions are often initiated by events, such as the arrival of new data over the wire or user interaction. Since transitions are not instantaneous — they have a duration — that could mean multiple transitions competing to control the fate of elements. To avoid this, transitions should be exclusive, allowing a newer transition to pre-empt (to interrupt) an older one.</p><p name="610b" id="610b" class="graf--p graf-after--p">However, such exclusivity should not be global. Multiple concurrent transitions should be allowed, as long as they operate on different elements. If you quickly toggle between stacked and grouped bars below, you can send waves rippling across the chart:</p><figure name="5d1e" id="5d1e" class="graf--figure graf-after--p"><div class="aspectRatioPlaceholder is-locked" style="max-width: 700px; max-height: 365px;"><div class="aspectRatioPlaceholder-fill" style="padding-bottom: 52.1%;"></div><a href="https://bl.ocks.org/mbostock/3943967" data-href="https://bl.ocks.org/mbostock/3943967" class="graf-imageAnchor"><div class="progressiveMedia js-progressiveMedia graf-image" data-image-id="1*78YxaN5t-cTAxaHdMveRUw.png" data-width="960" data-height="500"><img src="https://cdn-images-1.medium.com/freeze/max/40/1*78YxaN5t-cTAxaHdMveRUw.png?q=20" crossorigin="anonymous" class="progressiveMedia-thumbnail js-progressiveMedia-thumbnail"><canvas class="progressiveMedia-canvas js-progressiveMedia-canvas"></canvas><img class="progressiveMedia-image js-progressiveMedia-image" data-src="https://cdn-images-1.medium.com/max/1067/1*78YxaN5t-cTAxaHdMveRUw.png"><noscript class="js-progressiveMedia-inner"><img class="progressiveMedia-noscript js-progressiveMedia-inner" src="https://cdn-images-1.medium.com/max/1067/1*78YxaN5t-cTAxaHdMveRUw.png"></noscript></div></a></div><figcaption class="imageCaption"><a href="https://bl.ocks.org/mbostock/3943967" data-href="https://bl.ocks.org/mbostock/3943967" class="markup--anchor markup--figure-anchor" rel="nofollow">bl.ocks.org/3943967</a></figcaption></figure><p name="e207" id="e207" class="graf--p graf-after--figure">D3’s transitions are exclusive per element by default. If you need greater exclusivity, there’s <em class="markup--em markup--p-em">selection</em>.interrupt, which interrupts the active transition on the selected elements.</p><p name="9040" id="9040" class="graf--p graf-after--p">The problem with <em class="markup--em markup--p-em">selection</em>.interrupt in D3 3.x is that it does not also cancel pending transitions that are scheduled on selected elements. There’s not a good justification for this oversight — blame can likely be placed on a different design flaw in D3 3.x’s timers, which can’t be stopped externally, therefore making it somewhat awkward to cancel transitions. (Instead, pre-empted transitions self-terminate on start.)</p><p name="1f12" id="1f12" class="graf--p graf-after--p">The suggested workaround in D3 3.x is to create a no-op, zero-delay transition after interrupting:</p><pre name="f426" id="f426" class="graf--pre graf-after--p">selection<br> .interrupt() // interrupt the active transition<br> .transition(); // pre-empt any scheduled transitions</pre><p name="30d6" id="30d6" class="graf--p graf-after--pre">That <em class="markup--em markup--p-em">mostly</em> works. But you can cheat it by scheduling another transition:</p><pre name="f61c" id="f61c" class="graf--pre graf-after--p">selection<br> .transition()<br> .each(&quot;start&quot;, alert); // 🌶</pre><pre name="c57f" id="c57f" class="graf--pre graf-after--pre">selection<br> .interrupt()<br> .transition();</pre><p name="6725" id="6725" class="graf--p graf-after--pre">Since the first transition isn’t yet active, it’s not interrupted. And since the second transition is scheduled after the first transition, the first transition is allowed to start before it is subsequently interrupted.</p><p name="2873" id="2873" class="graf--p graf-after--p">In D3 4.0, <em class="markup--em markup--p-em">selection</em>.interrupt both interrupts the active transition, if any, and cancels all scheduled transitions. Cancellation is stronger the pre-emption: the scheduled transitions are immediately destroyed, freeing up resources, and guaranteeing they won’t start.</p><pre name="465d" id="465d" class="graf--pre graf-after--p">selection.interrupt();</pre><h4 name="6e79" id="6e79" class="graf--h4 graf-after--pre">Maxim 6. Consider all possible usage patterns.</h4><p name="22ab" id="22ab" class="graf--p graf-after--h4 graf--last">Asynchronous programming is notoriously difficult because the order of operations is highly unpredictable. While it is hard to implement robust and deterministic asynchronous APIs, surely it is harder to use brittle ones. The designer is responsible for being “thorough down to the last detail.”</p></div></div></section><section name="80dd" class=" section--body"><div class="section-divider layoutSingleColumn"><hr class="section-divider"></div><div class="section-content"><div class="section-inner layoutSingleColumn"><h4 name="3510" id="3510" class="graf--h4 graf--first">Case 7. Naming parameters.</h4><p name="6532" id="6532" class="graf--p graf-after--h4">I’ll end with an easy one. D3 4.0 includes a few syntax improvements intended to make code more readable and self-describing. Consider this code using D3 3.x:</p><pre name="88cd" id="88cd" class="graf--pre graf-after--p">selection.transition()<br> .duration(750)<br> .ease(&quot;elastic-out&quot;, 1, 0.3);</pre><p name="6007" id="6007" class="graf--p graf-after--pre">Some questions you may have:</p><ul class="postList"><li name="5ccf" id="5ccf" class="graf--li graf-after--p">What does the value 1 mean?</li><li name="342f" id="342f" class="graf--li graf-after--li">What does the value 0.3 mean?</li><li name="6f6b" id="6f6b" class="graf--li graf-after--li">What easing types besides “elastic-out” are supported?</li><li name="46b0" id="46b0" class="graf--li graf-after--li">Can I implement a custom easing function?</li></ul><p name="99fc" id="99fc" class="graf--p graf-after--li">Now compare to D3 4.0:</p><pre name="bb32" id="bb32" class="graf--pre graf-after--p">selection.transition()<br> .duration(750)<br> .ease(d3.easeElasticOut<br> .amplitude(1)<br> .period(0.3));</pre><p name="86dc" id="86dc" class="graf--p graf-after--pre">The meaning of 1 and 0.3 is now apparent, or at least you can now look up amplitude and period for elastic easing in the <a href="https://github.com/d3/d3-ease#easeElastic" data-href="https://github.com/d3/d3-ease#easeElastic" class="markup--anchor markup--p-anchor" rel="nofollow">API reference</a>, which helpfully includes this image:</p><figure name="2af8" id="2af8" class="graf--figure graf-after--p"><div class="aspectRatioPlaceholder is-locked" style="max-width: 700px; max-height: 284px;"><div class="aspectRatioPlaceholder-fill" style="padding-bottom: 40.5%;"></div><div class="progressiveMedia js-progressiveMedia graf-image" data-image-id="1*CzIlXgHWNgYTVOFJ9Vm0mQ.png" data-width="888" data-height="360" data-action="zoom" data-action-value="1*CzIlXgHWNgYTVOFJ9Vm0mQ.png"><img src="https://cdn-images-1.medium.com/freeze/max/40/1*CzIlXgHWNgYTVOFJ9Vm0mQ.png?q=20" crossorigin="anonymous" class="progressiveMedia-thumbnail js-progressiveMedia-thumbnail"><canvas class="progressiveMedia-canvas js-progressiveMedia-canvas"></canvas><img class="progressiveMedia-image js-progressiveMedia-image" data-src="https://cdn-images-1.medium.com/max/1067/1*CzIlXgHWNgYTVOFJ9Vm0mQ.png"><noscript class="js-progressiveMedia-inner"><img class="progressiveMedia-noscript js-progressiveMedia-inner" src="https://cdn-images-1.medium.com/max/1067/1*CzIlXgHWNgYTVOFJ9Vm0mQ.png"></noscript></div></div></figure><p name="eef9" id="eef9" class="graf--p graf-after--figure">Furthermore, there’s no longer a hard-coded set of easing names known by <em class="markup--em markup--p-em">transition</em>.ease; easing is always defined by a function. D3 4.0 still provides built-in easing functions, but hopefully it’s more obvious that you can roll your own custom easing function, too.</p><h4 name="0935" id="0935" class="graf--h4 graf-after--p">Maxim 7. Give hints.</h4><p name="e3b3" id="e3b3" class="graf--p graf-after--h4">Functions that take many arguments are obviously bad design. Humans shouldn’t be expected to memorize such elaborate definitions. (I can’t tell you how many times I’ve had to look up the arguments to context.arc when drawing to a 2D canvas.)</p><p name="89cf" id="89cf" class="graf--p graf-after--p graf--last">Since inception, D3 has favored named properties, using method chaining and single-argument getter-setter methods. But there’s still room for improvement. If code can’t be completely self-explanatory, at least it can point you to the right place in the documentation.</p></div></div></section><section name="f719" class=" section--body section--last"><div class="section-divider layoutSingleColumn"><hr class="section-divider"></div><div class="section-content"><div class="section-inner layoutSingleColumn"><h3 name="4c35" id="4c35" class="graf--h3 graf--first">What Is The Purpose of Good Software?</h3><p name="87e3" id="87e3" class="graf--p graf-after--h3">It’s not just about computing a result quickly and correctly. It’s not even just about concise or elegant notation.</p><p name="8030" id="8030" class="graf--p graf-after--p">Humans have powerful but limited cognitive capacity. Many actors compete for that capacity, such as small children. Most importantly, <strong class="markup--strong markup--p-strong">humans learn</strong>. I hope my examples showed how the design of programming interfaces has a strong effect on whether humans can understand code and by extension whether they can learn to be proficient.</p><p name="2a90" id="2a90" class="graf--p graf-after--p">But learning goes beyond proficiency with a given tool. If you can take what you learn in one domain and apply it to other domains, that knowledge is much more valuable. That’s why, for instance, D3 uses the standard document object model and not a specialized representation. A specialized representation could perhaps be more efficient, but in the future when tools change, time you spent acquiring specialized knowledge may be wasted!</p><p name="52c5" id="52c5" class="graf--p graf-after--p">I don’t want you to learn D3 for the sake of D3. I want you to learn how to explore data and communicate insights effectively.</p><p name="188e" id="188e" class="graf--p graf-after--p"><strong class="markup--strong markup--p-strong">Good software is approachable.</strong> It can be understood completely in independent, easy pieces. You don’t need to understand <em class="markup--em markup--p-em">everything</em> before you can understand <em class="markup--em markup--p-em">anything</em>.</p><p name="96bb" id="96bb" class="graf--p graf-after--p"><strong class="markup--strong markup--p-strong">Good software is consistent.</strong> It lets you take what you’ve learned about one part and extrapolate it to the rest. It doesn’t self-contradict. It is parsimonious, avoiding superfluous elements.</p><p name="2e49" id="2e49" class="graf--p graf-after--p"><strong class="markup--strong markup--p-strong">Good software explains itself.</strong> It has affordances for learning and discovery. It is role-expressive and minimizes hidden magic.</p><p name="3a08" id="3a08" class="graf--p graf-after--p"><strong class="markup--strong markup--p-strong">Good software teaches.</strong> It doesn’t just automate an existing task, but provides insight or imparts knowledge, such as a best practice or a new perspective on a problem.</p><p name="6c2c" id="6c2c" class="graf--p graf-after--p graf--last"><strong class="markup--strong markup--p-strong">Good software is for humans.</strong> It is cognizant of people and the reality in which they live. It does not expect elaborate and arbitrary rules to be memorized. It anticipates the need for learning and debugging.</p></div></p>
  461. </article>
  462. </section>
  463. <nav id="jumpto">
  464. <p>
  465. <a href="/david/blog/">Accueil du blog</a> |
  466. <a href="https://medium.com/@mbostock/what-makes-software-good-943557f8a488">Source originale</a> |
  467. <a href="/david/stream/2019/">Accueil du flux</a>
  468. </p>
  469. </nav>
  470. <footer>
  471. <div>
  472. <img src="/static/david/david-larlet-avatar.jpg" loading="lazy" class="avatar" width="200" height="200">
  473. <p>
  474. Bonjour/Hi!
  475. Je suis <a href="/david/" title="Profil public">David&nbsp;Larlet</a>, je vis actuellement à Montréal et j’alimente cet espace depuis 15 ans. <br>
  476. Si tu as apprécié cette lecture, n’hésite pas à poursuivre ton exploration. Par exemple via les <a href="/david/blog/" title="Expériences bienveillantes">réflexions bimestrielles</a>, la <a href="/david/stream/2019/" title="Pensées (dés)articulées">veille hebdomadaire</a> ou en t’abonnant au <a href="/david/log/" title="S’abonner aux publications via RSS">flux RSS</a> (<a href="/david/blog/2019/flux-rss/" title="Tiens c’est quoi un flux RSS ?">so 2005</a>).
  477. </p>
  478. <p>
  479. Je m’intéresse à la place que je peux avoir dans ce monde. En tant qu’humain, en tant que membre d’une famille et en tant qu’associé d’une coopérative. De temps en temps, je fais aussi des <a href="https://github.com/davidbgk" title="Principalement sur Github mais aussi ailleurs">trucs techniques</a>. Et encore plus rarement, <a href="/david/talks/" title="En ce moment je laisse plutôt la place aux autres">j’en parle</a>.
  480. </p>
  481. <p>
  482. Voici quelques articles choisis :
  483. <a href="/david/blog/2019/faire-equipe/" title="Accéder à l’article complet">Faire équipe</a>,
  484. <a href="/david/blog/2018/bivouac-automnal/" title="Accéder à l’article complet">Bivouac automnal</a>,
  485. <a href="/david/blog/2018/commodite-effondrement/" title="Accéder à l’article complet">Commodité et effondrement</a>,
  486. <a href="/david/blog/2017/donnees-communs/" title="Accéder à l’article complet">Des données aux communs</a>,
  487. <a href="/david/blog/2016/accompagner-enfant/" title="Accéder à l’article complet">Accompagner un enfant</a>,
  488. <a href="/david/blog/2016/senior-developer/" title="Accéder à l’article complet">Senior developer</a>,
  489. <a href="/david/blog/2016/illusion-sociale/" title="Accéder à l’article complet">L’illusion sociale</a>,
  490. <a href="/david/blog/2016/instantane-scopyleft/" title="Accéder à l’article complet">Instantané Scopyleft</a>,
  491. <a href="/david/blog/2016/enseigner-web/" title="Accéder à l’article complet">Enseigner le Web</a>,
  492. <a href="/david/blog/2016/simplicite-defaut/" title="Accéder à l’article complet">Simplicité par défaut</a>,
  493. <a href="/david/blog/2016/minimalisme-esthetique/" title="Accéder à l’article complet">Minimalisme et esthétique</a>,
  494. <a href="/david/blog/2014/un-web-omni-present/" title="Accéder à l’article complet">Un web omni-présent</a>,
  495. <a href="/david/blog/2014/manifeste-developpeur/" title="Accéder à l’article complet">Manifeste de développeur</a>,
  496. <a href="/david/blog/2013/confort-convivialite/" title="Accéder à l’article complet">Confort et convivialité</a>,
  497. <a href="/david/blog/2013/testament-numerique/" title="Accéder à l’article complet">Testament numérique</a>,
  498. et <a href="/david/blog/" title="Accéder aux archives">bien d’autres…</a>
  499. </p>
  500. <p>
  501. On peut <a href="mailto:david%40larlet.fr" title="Envoyer un courriel">échanger par courriel</a>. Si éventuellement tu souhaites que l’on travaille ensemble, tu devrais commencer par consulter le <a href="http://larlet.com">profil dédié à mon activité professionnelle</a> et/ou contacter directement <a href="http://scopyleft.fr/">scopyleft</a>, la <abbr title="Société coopérative et participative">SCOP</abbr> dont je fais partie depuis six ans. Je recommande au préalable de lire <a href="/david/blog/2018/cout-site/" title="Attention ce qui va suivre peut vous choquer">combien coûte un site</a> et pourquoi je suis plutôt favorable à une <a href="/david/pro/devis/" title="Discutons-en !">non-demande de devis</a>.
  502. </p>
  503. <p>
  504. Je ne traque pas ta navigation mais mon
  505. <abbr title="Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33.184162340">hébergeur</abbr>
  506. conserve des logs d’accès.
  507. </p>
  508. </div>
  509. </footer>
  510. <script type="text/javascript">
  511. ;(_ => {
  512. const jumper = document.getElementById('jumper')
  513. jumper.addEventListener('click', e => {
  514. e.preventDefault()
  515. const anchor = e.target.getAttribute('href')
  516. const targetEl = document.getElementById(anchor.substring(1))
  517. targetEl.scrollIntoView({behavior: 'smooth'})
  518. })
  519. })()
  520. </script>