@flourish/colors
Advanced tools
Comparing version 6.1.2 to 6.1.4
{ | ||
"name": "@flourish/colors", | ||
"version": "6.1.2", | ||
"version": "6.1.4", | ||
"description": "Adds color settings", | ||
@@ -12,3 +12,4 @@ "main": "colors.js", | ||
"minify": "uglifyjs -m -o colors.min.js colors.js", | ||
"precommit": "lint-staged" | ||
"precommit": "lint-staged", | ||
"test": "npm run build; mocha" | ||
}, | ||
@@ -19,5 +20,7 @@ "author": "Kiln Enterprises Ltd", | ||
"@flourish/eslint-plugin-flourish": "^0.7.2", | ||
"chai": "^4.3.6", | ||
"eslint": "^4.19.1", | ||
"husky": "^0.14.3", | ||
"lint-staged": "^7.3.0", | ||
"mocha": "^10.0.0", | ||
"rollup": "^0.41.1", | ||
@@ -24,0 +27,0 @@ "rollup-plugin-node-resolve": "^3.3.0", |
@@ -9,2 +9,12 @@ # Flourish colors | ||
## Unit tests | ||
Test using: | ||
`npm run test` | ||
Test a specific test file using: | ||
`npx mocha test/name-of-test.test.mjs` | ||
## Basic usage | ||
@@ -47,2 +57,2 @@ | ||
`colors.updateColorScale(domain)` updates the color scale based on the `domain` array. | ||
`colors.updateColorScale(domain)` updates the color scale based on the `domain` array. In general the domain array is an array of data values that the scale should be based on. For a categorical scale, this'll be an array of strings. For numeric (sequential or diverging) scales, this'll be an array of numbers (e.g. `[10, 50, 30, 20, 50, 10, 30, 60, 100, 90]`). |
@@ -0,1 +1,7 @@ | ||
# 6.1.4 | ||
* Return null from color functions if null is input | ||
# 6.1.3 | ||
* Improve binning of custom diverging scales | ||
# 6.1.2 | ||
@@ -2,0 +8,0 @@ * Set categorical color scales for all non-numeric typed domains |
@@ -1,40 +0,186 @@ | ||
import { scaleQuantize, scaleQuantile, scaleThreshold } from "d3-scale"; | ||
import { scaleLinear, scaleQuantize, scaleQuantile, scaleThreshold } from "d3-scale"; | ||
import { range } from "d3-array"; | ||
export function binnedScale(state, domain, values, interpolator) { | ||
function ascendingSort(a, b) { return a - b; } | ||
function getFixedSizeThresholds(state, domain) { | ||
var domain_min = domain[0]; | ||
var domain_max = domain[domain.length - 1]; | ||
var centers = [], thresholds, n; | ||
if (state.bin_mode == "custom") { | ||
thresholds = state.bin_thresholds | ||
.split(";") | ||
.map(parseFloat) | ||
.filter(function (val) { return !isNaN(val); }) | ||
.sort(ascendingSort); | ||
n = thresholds.length + 1; | ||
var scale = scaleQuantize().domain([domain_min, domain_max]); | ||
var n = Math.floor(state.bin_count); | ||
scale.range(range(n)); | ||
var thresholds = scale.thresholds(); | ||
return thresholds; | ||
} | ||
function getQuantileThresholds(state, domain, values) { | ||
var scale = scaleQuantile().domain(values); | ||
var n = Math.floor(state.bin_count); | ||
scale.range(range(n)); | ||
var thresholds = scale.quantiles(); | ||
return thresholds; | ||
} | ||
function getCustomThresholds(state) { | ||
var thresholds = state.bin_thresholds | ||
.split(";") | ||
.map(parseFloat) | ||
.filter(function (val) { return !isNaN(val) && val !== null; }) | ||
.sort(ascendingSort); | ||
return thresholds; | ||
} | ||
function getThresholds(state, domain, values) { | ||
var thresholds; | ||
if (state.bin_mode == "fixed") { | ||
thresholds = getFixedSizeThresholds(state, domain, values); | ||
} | ||
else if (state.bin_mode == "quantile") { | ||
thresholds = getQuantileThresholds(state, domain, values); | ||
} | ||
else { | ||
n = Math.floor(state.bin_count); | ||
thresholds = []; | ||
thresholds = getCustomThresholds(state); | ||
} | ||
var palette = interpolatePalette(interpolator, n, domain); | ||
var scale; | ||
if (state.bin_mode == "fixed") scale = scaleQuantize().domain([domain_min, domain_max]); | ||
else if (state.bin_mode == "quantile") scale = scaleQuantile().domain(values); | ||
else scale = scaleThreshold().domain(thresholds); | ||
scale.range(palette.colors); | ||
return thresholds; | ||
} | ||
if (!thresholds.length) { | ||
palette.colors.forEach(function (color, i) { | ||
if (i) thresholds.push(scale.invertExtent(color)[0]); | ||
}); | ||
function getBinCenters(min, thresholds, max) { | ||
var centers = []; | ||
if (thresholds.length === 0) return [0.5 * (min + max)]; | ||
centers.push(0.5 * (min + thresholds[0])); | ||
for (var i = 0; i < thresholds.length - 1; i++) { | ||
centers.push(0.5 * (thresholds[i] + thresholds[i + 1])); | ||
} | ||
centers.push(0.5 * (max + thresholds[thresholds.length - 1])); | ||
thresholds.forEach(function (threshold, i) { | ||
if (i < (thresholds.length - 1)) centers.push((thresholds[i + 1] + threshold) / 2); | ||
return centers; | ||
} | ||
function getIndexOfBinCenterAtDomainMid(centers, domain_mid) { | ||
var found_index, tolerance = 1e-3; | ||
centers.forEach(function(d, i) { | ||
if (Math.abs(d - domain_mid) < tolerance) { | ||
found_index = i; | ||
} | ||
}); | ||
return found_index; | ||
} | ||
function getNumberOfPreMidBinCenters(centers, domain_mid) { | ||
var num = 0; | ||
centers.forEach(function(d) { | ||
if (d < domain_mid) num++; | ||
}); | ||
return num; | ||
} | ||
function pushSamples(n, offset, sample_scale, colorInterpolator, colors) { | ||
for (var i = 1 + offset; i <= n + offset; i++) { | ||
colors.push(colorInterpolator(sample_scale(i))); | ||
} | ||
} | ||
function sampleColors(domain, bin_centers, colorInterpolator) { | ||
// Sample colors from colorInterpolator | ||
// Sequential is straightforward (just sample evenly) | ||
// Diverging is less straightforward (sample evenly below 0.5 and above 0.5 of the colorInterpolator) | ||
var n_bins = bin_centers.length; | ||
var sample_scale = scaleLinear(); // sample_scale is used to evenly sample colors across a domain | ||
var colors = []; | ||
if (n_bins === 1) { | ||
colors.push(colorInterpolator(0.5)); | ||
} | ||
else if (domain.length === 2) { | ||
// SEQUENTIAL | ||
sample_scale.domain([1, n_bins]).range([0, 1]); | ||
pushSamples(n_bins, 0, sample_scale, colorInterpolator, colors); | ||
} | ||
else if (n_bins === 2) { | ||
// DIVERGING (2 bins) | ||
// Use the min/max values of colorInterpolator | ||
colors.push(colorInterpolator(0)); | ||
colors.push(colorInterpolator(1)); | ||
} | ||
else { | ||
// DIVERGING (>2 bins) | ||
var domain_mid = domain[1]; | ||
var pre_mid_n; // number of bin centers < domain_mid | ||
var post_mid_n; // number of bin centers > domain_mid | ||
// mid_i is the index within bin_centers of domain_mid (returns undefined if non-existent) | ||
var mid_i = getIndexOfBinCenterAtDomainMid(bin_centers, domain_mid); | ||
if (mid_i) { | ||
// Special case: one of the bin centers is equal to domain_mid | ||
// Sample color scale at: | ||
// - the first pre_mid_n samples from (pre_mid_n + 1) samples between 0 and 0.5 | ||
// - 0.5 | ||
// - the last post_mid_n samples from (post_mid_n + 1) samples between 0.5 and 1 | ||
pre_mid_n = mid_i; | ||
post_mid_n = n_bins - mid_i - 1; | ||
sample_scale.domain([1, pre_mid_n + 1]).range([0, 0.5]); | ||
pushSamples(pre_mid_n, 0, sample_scale, colorInterpolator, colors); | ||
colors.push(colorInterpolator(0.5)); | ||
sample_scale.domain([1, post_mid_n + 1]).range([0.5, 1]); | ||
pushSamples(post_mid_n, 1, sample_scale, colorInterpolator, colors); | ||
} | ||
else { | ||
// Take color samples either side of colorInterpolator's midpoint (0.5) | ||
pre_mid_n = getNumberOfPreMidBinCenters(bin_centers, domain_mid); | ||
post_mid_n = n_bins - pre_mid_n; | ||
sample_scale.domain([1, pre_mid_n + 1]).range([0, 0.5]); | ||
pushSamples(pre_mid_n, 0, sample_scale, colorInterpolator, colors); | ||
sample_scale.domain([1, post_mid_n + 1]).range([0.5, 1]); | ||
pushSamples(post_mid_n, 1, sample_scale, colorInterpolator, colors); | ||
} | ||
} | ||
return colors; | ||
} | ||
export function binnedScale(state, domain, values, colorInterpolator) { | ||
// Returns a binned scale | ||
// colorInterpolator (whose domain is [0, 1]) defines the colour range | ||
// domain is a 2 or 3 element array that defines the domain of the input data | ||
// values is the input data | ||
// The method is: | ||
// - get thresholds across domain (getThresholds) | ||
// - compute midpoints between each threshold (getThresholdMidpoints) | ||
// - sample evenly across colour scale (sampleColors) | ||
// - return quantile or threshold scale using colour samples as range | ||
var thresholds = getThresholds(state, domain, values); | ||
var domain_min = domain[0]; | ||
var domain_max = domain[domain.length - 1]; | ||
var bin_centers = getBinCenters(domain_min, thresholds, domain_max); | ||
var colors = sampleColors(domain, bin_centers, colorInterpolator); | ||
var scale; | ||
if (state.bin_mode == "quantile") { | ||
scale = scaleQuantile().domain(values); | ||
} | ||
else { | ||
scale = scaleThreshold().domain(thresholds); | ||
} | ||
scale.range(colors); | ||
var colorScale = function (value) { | ||
return !isNaN(value) ? scale(value) : null; | ||
return isNaN(value) || value === null ? null : scale(value); | ||
}; | ||
@@ -44,19 +190,5 @@ | ||
colorScale.thresholds = Object.freeze(thresholds); | ||
colorScale.centers = Object.freeze(centers); | ||
colorScale.centers = Object.freeze(bin_centers); | ||
return colorScale; | ||
} | ||
function interpolatePalette(interpolator, n, domain) { | ||
var step = 1 / n; | ||
var positions = []; | ||
for (var i = 0; i < n; i++) { | ||
var pos = i / n + step / 2; | ||
var val = domain[0] + pos * (domain[domain.length - 1] - domain[0]); | ||
positions.push(val); | ||
} | ||
var colors = positions.map(interpolator); | ||
return { positions: positions, colors: colors }; | ||
} | ||
function ascendingSort(a, b) { return a - b; } |
@@ -41,7 +41,6 @@ import { scaleDiverging } from "d3-scale"; | ||
if (state.diverging_custom_domain) { | ||
if (state.binning) domain[1] = state.diverging_domain_mid; | ||
else domain = [state.diverging_domain_min, state.diverging_domain_mid, state.diverging_domain_max]; | ||
domain = [state.diverging_domain_min, state.diverging_domain_mid, state.diverging_domain_max]; | ||
} | ||
var interpolate = function(t) { | ||
var colorInterpolator = function(t) { | ||
var interpolation = INTERPOLATORS[state.diverging_palette] || getCustomInterpolator(state); | ||
@@ -52,5 +51,7 @@ if (state.diverging_reverse) return interpolation(1 - t); | ||
var scale = scaleDiverging(interpolate).domain(domain); | ||
if (state.binning) return binnedScale(state, domain, values, colorInterpolator); | ||
var scale = scaleDiverging(colorInterpolator).domain(domain); | ||
var colorScale = function (value) { | ||
return !isNaN(value) ? scale(value) : null; | ||
return isNaN(value) || value === null ? null : scale(value); | ||
}; | ||
@@ -61,4 +62,3 @@ | ||
if (!state.binning) return colorScale; | ||
else return binnedScale(state, domain, values, colorScale); | ||
return colorScale; | ||
} | ||
@@ -65,0 +65,0 @@ |
@@ -53,4 +53,4 @@ import { scaleSequential } from "d3-scale"; | ||
function getSequentialFunction(state, values) { | ||
var domain = state.sequential_custom_domain && !state.binning ? [state.sequential_domain_min, state.sequential_domain_max] : getDomain(values); | ||
var interpolate = function (t) { | ||
var domain = state.sequential_custom_domain ? [state.sequential_domain_min, state.sequential_domain_max] : getDomain(values); | ||
var colorInterpolator = function (t) { | ||
var interpolation = INTERPOLATORS[state.sequential_palette] || getCustomInterpolator(state); | ||
@@ -60,11 +60,14 @@ if (state.sequential_reverse) return interpolation(1 - t); | ||
}; | ||
var scale = scaleSequential(interpolate).domain(domain); | ||
if (state.binning) return binnedScale(state, domain, values, colorInterpolator); | ||
var scale = scaleSequential(colorInterpolator).domain(domain); | ||
var colorScale = function (value) { | ||
return !isNaN(value) ? scale(value) : null; | ||
return isNaN(value) || value === null ? null : scale(value); | ||
}; | ||
colorScale.domain = Object.freeze(domain); | ||
if (!state.binning) return colorScale; | ||
else return binnedScale(state, domain, values, colorScale); | ||
return colorScale; | ||
} | ||
export { getSequentialFunction }; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
169177
21
4316
56
9