├── .eslintrc
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── index.html
├── index.js
├── package.json
├── src
└── jnd.js
└── test
└── jnd-test.js
/.eslintrc:
--------------------------------------------------------------------------------
1 | parserOptions:
2 | sourceType: module
3 |
4 | env:
5 | node: true
6 |
7 | extends:
8 | "eslint:recommended"
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | build/
3 | node_modules/
4 | npm-debug.log
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | build/*.zip
2 | test/
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2016 Connor Gramazio
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | * Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | * Neither the name of the author nor the names of contributors may be used to
15 | endorse or promote products derived from this software without specific prior
16 | written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # d3-jnd
2 |
3 | To access the latest version of d3-jnd, grab it on
4 | [unpkg](https://unpkg.com/d3-jnd). Note that d3-jnd depends on
5 | [d3-color](https://github.com/d3/d3-color).
6 |
7 | This module extends D3 to support color just-noticeable difference (JND)
8 | research by Maureen Stone, Danielle Albers Szafir, and Vidya Setlur ([link](https://research.tableau.com/paper/engineering-model-color-difference-function-size)).
9 |
10 | ```
11 | @inproceedings{stone-2014-emc,
12 | title={An engineering model for color difference as a function of size},
13 | author={Stone, Maureen and Szafir, Danielle Albers and Setlur, Vidya},
14 | booktitle={Color and Imaging Conference},
15 | volume={2014},
16 | number={2014},
17 | pages={253--258},
18 | year={2014},
19 | organization={Society for Imaging Science and Technology}
20 | }
21 | ```
22 |
23 | d3-jnd has two functions: ``d3.noticeablyDifferent()`` and ``d3.jndInterval``.
24 | Both take ``size`` and ``percent`` arguments, which alter how conservative JND
25 | estimations are.
26 |
27 | Note that the recommendations that are provided by this library are predicted
28 | JND intervals, and should be treated as guidelines rather than absolute truth.
29 | See the [d3-jnd website](https://connorgr.github.io/d3-jnd) for more information
30 | about how to manipulate d3-jnd function arguments appropriately.
31 |
32 | ```js
33 | var c1 = d3.lab('black'),
34 | c2 = d3.rgb('white'),
35 | legible = d3.noticeablyDifferent(c1, c2); // true
36 |
37 | var intervals = d3.jndInterval(0.5, 0.3);
38 |
39 | var strictlyLegible = d3.noticeablyDifferent(c1, c2, 0.95, 0.1);
40 | ```
41 |
42 | # Installation
43 |
44 | After downloading the repo, run ``npm install``, which will install any
45 | dependencies. You can optionally install from npm opposed to cloning directly
46 | from GitHub. Make sure to load d3-cam02 after d3-color.
47 |
48 | **Dependencies:** [d3-color](https://github.com/d3/d3-color)
49 |
50 | ## API Reference
51 |
52 | # d3.noticeablyDifferent(color, color[, percent, size]) [<>](https://github.com/connorgr/d3-jnd/blob/master/src/jnd.js#L78 "Source")
53 |
54 | A function that returns true/false based on whether the two provided colors are
55 | noticeably different. Percent controls the percent of audience that will likely
56 | see a difference between the two colors quickly and easily.
57 | Size refers to the estimated visual angle of color area (smaller areas are
58 | harder to discriminate).
59 |
60 | # d3.jndInterval(percent, size) [<>](https://github.com/connorgr/d3-jnd/blob/master/src/jnd.js#L64 "Source")
61 |
62 | As d3.noticeablyDifferent except it returns the intervals along L*, a*, and b*
63 | channels of CIELAB that produce differences equal to "1 JND".
64 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
d3-jnd is a 187 | D3 extension 188 | that supports 189 | just-noticeable 190 | difference calculations for color based on work done by Maureen Stone, 191 | Danielle Albers Szafir, and Vidya Setlur [1].
192 | 193 |d3-jnd is open sourced 194 | here. If you want to 195 | look at or use the compiled extension without downloading the source code, 196 | check it out on unpkg.
197 | 198 |The just-noticeable difference functions in this library can be used to 199 | determine whether two colors are too similar to be easily differentiated. 200 | This is often critical for visualization color design because information 201 | legibility directly affects usability. 202 |
203 | 204 |The two functions in d3-jnd are: 205 |
d3.noticeablyDifferent(…)
, which returns true or false
207 | based on whether the two colors are noticeably different (i.e., ≥1
208 | JND distant).d3.jndLabInterval(…)
, which returns the minimum JND
210 | intervals for CIELAB L*, a*, and b* color channels.
211 | Each interval can be thought of as "1 JND" unit for that channel, and
212 | it is often sufficient to only have one channel that is ≥1 JND to
213 | maintain legibility.
214 | Be sure to check that colors that meet the recommended L*, a*, and b* 226 | intervals still fall within displayable RGB color space when considering the 227 | intervals that jndLabInterval provides.
228 | 229 | 230 | Table of contents 231 |To access the latest version of d3-jnd, grab it on 253 | unpkg. Be sure to load d3-jnd 254 | after d3-color is loaded (be it through d3.v4.js, d3-color.js, or 255 | otherwise).
256 |If installing and building from scratch, download the repo either through
257 | GitHub or through
258 | npm.
259 | Once you've downloaded it, just run npm install
, which will
260 | install the required dependencies.
261 | Note that building requires
262 | node.js and
263 | npm.
D3 module dependencies: 266 | d3-color.
267 |A just-noticeable difference (JND) is, "the amount [that a color] must be 272 | changed in order for a difference to be noticeable" 273 | [2]. 274 | Or, phrased another way, how different do two colors need to be in order for 275 | humans to recognize them as different? 276 | Given that this differentiability is directly dependent on human color vision, 277 | d3-jnd performs JND calculations using CIELAB, which is a perceptually 278 | modeled color space.
279 | 280 | 281 |CIELAB defines color in terms of its perceptual lightness (L*), 284 | redness-to-greenness (a*), and blueness-to-yellowness (b*). 285 | d3-jnd does color conversion for you, so it's not critical to become an 286 | expert if you're just using the noticeablyDifferent function. 287 | If you're interested in learning more, check out Wikipedia's entry on 288 | CIELAB [3] or the 289 | d3-cam02 library to learn 290 | more about CIECAM02, which is an updated and more accurate perceptual 291 | color space. 292 | In this library we use CIELAB because it is what Stone, Szafir, and Setlur 293 | used in their experiments to derive regression-based JND formulas. 294 |
295 |Unfortunately, determining whether two colors are discriminable is an 299 | involved process in part because it is harder to discriminate colors that 300 | are smaller in area. You can see this for yourself in the example below.
301 | 302 |Because size in this context is a physical property that can greatly vary 317 | with viewing conditions (e.g., dpi or viewing distance with 318 | phones compared to laptops), 319 | d3-jnd uses visual angle as a measurement of size rather than pixels. 320 | At arms length, the width of a thumb's knuckle is approximately 2° and an 321 | index fingernail is approximately 1°. 322 | d3-jnd uses a default colored area size assumption of 0.1°, which was 323 | selected as a conservatively small size without resulting in overly 324 | restrictive color selection; however, the default assumed size can be 325 | overridden to create more generous or strict JND distances. 326 |
327 | 328 |The physical size of visualizations can affect usability in a 331 | number of ways that include, but are not limited to, color legibility. 332 | If you're interested in 333 | learning more, we suggest Connor C. Gramazio, Karen B. Schloss, David H. 334 | Laidlaw's paper on size's affect on visualization search 335 | [4] and Maureen Stone's IEEE Viewpoint 336 | article [5]. 337 |
338 |Due to differences between people's perceptual acuity, the JND intervals 343 | that d3-jnd provides are predictions, not absolute truth. 344 | To accomodate this ambiguity, the d3-jnd functions' percentage argument can 345 | be used to incorporate what percentage of your audience should quickly and 346 | easily notice a difference in color. 347 | The default value used by d3-jnd is 50%, given that using 50% to refer to 348 | JNDs is standard practice in perception research; however, whether that 349 | threshold is sufficient for a visualization is contextually dependent. 350 | We encourage d3-jnd users to carefully consider whether they need to use 351 | a more conservative number (e.g., require JND intervals where an estimated 352 | 95% of users could reliably differentiate colors). 353 |
354 | 355 |Another important individual difference to consider is people's different 358 | perceptions of color (e.g., red-green or blue-yellow color vision 359 | deficiencies). Given that a large portion of the population perceive color 360 | differently, consider whether the recommended L*, a*, or *b intervals you 361 | choose to use are sufficient. For example, a* modulates the 362 | redness-to-greeness of color and differences along only this channel might 363 | not be visible to all. 364 |
365 |Although two colors might appear discriminable on your own monitor, 370 | consider whether that discriminability would be visible on your audience's 371 | displays. Developers and designers often use bright monitors that support 372 | wide color gamuts (e.g., any Apple device), which is not representative of 373 | displays used by the general population. Further, color can be aversely 374 | affected by environment (e.g., indoor vs. outdoor lighting). 375 | It is important to treat d3-jnd as only a prediction or recommendation of 376 | what may be noticeably different rather than an absolute truth.
377 |Interactive demo of how changing percent and size JND arguments can alter L*, a*, and b* intervals.
382 |Provided free-to-use and open-sourced. See Github for specific licensing information.
405 | 406 | 407 | 408 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | export {default as jndLabInterval, noticeablyDifferent} from "./src/jnd"; 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "d3-jnd", 3 | "version": "0.1.3", 4 | "description": "Just-noticeable differences for colors in CEILAB color space", 5 | "keywords": [ 6 | "d3", 7 | "d3-module", 8 | "color", 9 | "jnd", 10 | "legibility", 11 | "discriminability", 12 | "just noticeable difference", 13 | "CIELAB", 14 | "Lab" 15 | ], 16 | "homepage": "https://github.com/connorgr/d3-jnd", 17 | "license": "BSD-3-Clause", 18 | "author": "Connor Gramazio (http://gramaz.io)", 19 | "main": "build/d3-jnd.js", 20 | "module": "index", 21 | "jsnext:main": "index", 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/connorgr/d3-jnd.git" 25 | }, 26 | "scripts": { 27 | "pretest": "rm -rf build && mkdir build && rollup --banner \"$(preamble)\" -f umd -g d3-color:d3 -n d3 -o build/d3-jnd.js -- index.js", 28 | "test": "tape 'test/**/*-test.js' && eslint index.js src test", 29 | "prepublish": "npm run test && uglifyjs --preamble \"$(preamble)\" build/d3-jnd.js -c -m -o build/d3-jnd.min.js", 30 | "postpublish": "git push && git push --tags && zip -j build/d3-jnd.zip -- LICENSE README.md build/d3-jnd.js build/d3-jnd.min.js" 31 | }, 32 | "dependencies": { 33 | "d3-color": "1" 34 | }, 35 | "devDependencies": { 36 | "eslint": "3", 37 | "package-preamble": "0.0", 38 | "rollup": "0.34", 39 | "tape": "4", 40 | "uglify-js": "2" 41 | }, 42 | "bugs": { 43 | "url": "https://github.com/connorgr/d3-jnd/issues" 44 | }, 45 | "directories": { 46 | "test": "test" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/jnd.js: -------------------------------------------------------------------------------- 1 | // Implementation based on Maureen Stone, Danielle Albers Szafir, and Vidya 2 | // Setlur's paper "An Engineering Model for Color Difference as a Function of 3 | // Size" presented at the Color Imaging Conference, and can be found online at 4 | // https://research.tableau.com/sites/default/files/2014CIC_48_Stone_v3.pdf 5 | // 6 | // Their paper examines target sizes (visual angle) ranging from 6 to 1/3 7 | // degree, so note that extrapolations outside that range contain additional 8 | // untesteed assumptions about color appearence. 9 | 10 | // To calculate whether colors are noticeably different, colors are translated 11 | // into CIELAB perceptual color space. Further, users must specifiy a visual 12 | // angle for how large the colored elements are (e.g., bars in a bar chart) 13 | // along their smallest dimension (e.g., width for 25px wide x 100px tall bars). 14 | 15 | // Variable definitions: 16 | // nd: noticeable difference 17 | // p: a threshold defined as the percentage of observers who see two colors 18 | // separated by a particular color space interval (e.g., along L*) as 19 | // different. 20 | // s: size, specified in degrees of visual angle 21 | 22 | //----------------------------------------. 23 | // PREDICTING DISCRIMINABILITY THRESHOLDS \___________________________________ 24 | //=============================================================================| 25 | // // p = V(s)*Delta_D + e (i.e., y=ax+b), where 26 | // s: size, 27 | // V(s) and D: vector values of L*, a*, b* 28 | // e: error term 29 | // Delta_D: a step in CIELAB space 30 | // V(s): a vector of three slopes, which differ along L*, a*, and b* 31 | // 32 | // Therefore, Delta_D = nd(p) = p / V(s) 33 | // 34 | // For calculating just noticeable differences (JND), we'll assume that p should 35 | // be fixed at 50%, which then leaves size as the only free variable for 36 | // calculating discriminability intervals along L*, a*, and b* color channels. 37 | // 38 | // ND(50, s) = C(50) + K(50)/s, where C and K are regression coefficients 39 | // 40 | // Stone et al. also provide a generalized formula that can support p and s both 41 | // as free variables based on additional regressions (see paper): 42 | // 43 | // ND(p,s) = p(A+B/s), where 44 | // s: size, 45 | // p: % of observers who see colors as different ([0,1]) 46 | // A and B: preset values that differ for each channel 47 | // 48 | import {lab} from "d3-color"; 49 | 50 | 51 | 52 | 53 | function nd(p,s) { 54 | var A = {l: 10.16, a: 10.68, b: 10.70}, 55 | B = {l: 1.50, a: 3.08, b: 5.74}; 56 | 57 | return { 58 | l: p * (A.l + B.l / s), 59 | a: p * (A.a + B.a / s), 60 | b: p * (A.b + B.b / s) 61 | }; 62 | } 63 | 64 | export default function jndLabInterval(p, s) { 65 | if(typeof s === "string") { 66 | if(s === "thin") s = 0.1; 67 | else if(s === "medium") s = 0.5; 68 | else if(s === "wide") s = 1.0; 69 | else s = 0.1; 70 | } 71 | if(typeof p === "string") { 72 | if(s === "conservative") p = 0.8; 73 | else p = 0.5; 74 | } 75 | return nd(p, s); 76 | } 77 | 78 | export function noticeablyDifferent(c1, c2, s, p) { 79 | if(arguments.length < 3) s = 0.1; 80 | if(arguments.length < 4) p = 0.5; 81 | 82 | var jnd = jndLabInterval(p, s); 83 | c1 = lab(c1); 84 | c2 = lab(c2); 85 | 86 | return (Math.abs(c1.l-c2.l) >= jnd.l) || (Math.abs(c1.a-c2.a) >= jnd.a) || (Math.abs(c1.b-c2.b) >= jnd.b); 87 | } 88 | -------------------------------------------------------------------------------- /test/jnd-test.js: -------------------------------------------------------------------------------- 1 | var tape = require("tape"), 2 | d3_color = require("d3-color"), 3 | d3_jnd = require("../"); 4 | 5 | tape("noticeablyDifferent(…) returns correct responses", function(test) { 6 | var c1 = d3_color.lab(100, 0, 0), 7 | c2 = d3_color.lab(0,0,0); 8 | test.equal(d3_jnd.noticeablyDifferent(c1, c2), true); 9 | c2 = c1; 10 | test.equal(d3_jnd.noticeablyDifferent(c1, c2), false); 11 | test.end(); 12 | }); 13 | 14 | // There are no tests for jndInterval, because published interval values are for 15 | // Stone et al.'s ND(size) function rather than their generalized 16 | // ND(percent, size) function, which is used in d3-jnd. 17 | --------------------------------------------------------------------------------