Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Sequential colors #66

Closed
wants to merge 7 commits into from
146 changes: 118 additions & 28 deletions packages/contrast-colors/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,41 +239,30 @@ function removeDuplicates(originalArray, prop) {
return newArray;
}

function normalizePercent(min, max, input) {
let range = max - min;
let correctedStartValue = input - min;
let percentage = (correctedStartValue * 100) / range;
return percentage / 100;
}

function createScale({
swatches,
colorKeys,
colorspace = 'LAB',
shift = 1,
fullScale = true,
smooth = false
smooth = false,
correctLightness = true
} = {}) {
const space = colorSpaces[colorspace];
if (!space) {
throw new Error(`Colorspace “${colorspace}” not supported`);
}

let domains = colorKeys
.map(key => swatches - swatches * (d3.hsluv(key).v / 100))
.sort((a, b) => a - b)
.concat(swatches);

domains.unshift(0);

// Test logarithmic domain (for non-contrast-based scales)
let sqrtDomains = d3.scalePow()
.exponent(shift)
.domain([1, swatches])
.range([1, swatches]);

sqrtDomains = domains.map((d) => {
if (sqrtDomains(d) < 0) {
return 0;
}
return sqrtDomains(d);
});

// Transform square root in order to smooth gradient
domains = sqrtDomains;
let domains;
let ColorsArray = [];
let scale;

let sortedColor = colorKeys
// Convert to HSLuv and keep track of original indices
Expand All @@ -291,14 +280,39 @@ function createScale({
// Retrieve original RGB color
.map(data => colorKeys[data.index]);

let ColorsArray = [];

let scale;
if (fullScale) {
domains = colorKeys
.map(key => swatches - swatches * (d3.hsluv(key).v / 100))
.sort((a, b) => a - b)
.concat(Number(swatches));

domains.unshift(0);

ColorsArray = [space.white || '#fff', ...sortedColor, space.black || '#000'];
} else {
}
else if (!fullScale) {
let tempDomains = colorKeys
.map(key => swatches - swatches * (d3.hsluv(key).v / 100))
.sort((a, b) => a - b);

let min = Math.min(...tempDomains);
let max = Math.max(...tempDomains);

domains = tempDomains.map(key => normalizePercent(min, max, key) * swatches);

ColorsArray = sortedColor;
}

if(!correctLightness) {
domains = [];
for (let i=0; i < ColorsArray.length; i++) {
let p = 1 / (ColorsArray.length - 1);
let c = i * p;
domains.push(c * swatches);
}
}

const stringColors = ColorsArray;
ColorsArray = ColorsArray.map(d => d3[space.name](d));
if (space.name == 'hcl') {
Expand All @@ -316,16 +330,50 @@ function createScale({
}
}

// Test logarithmic domain (for non-contrast-based scales)
let sqrtDomains = d3.scalePow()
.exponent(shift)
.domain([0, swatches])
.range([0, swatches]);

sqrtDomains = domains.map((d) => {
if (sqrtDomains(d) < 0) {
return 0;
}
return sqrtDomains(d);
});

// Transform square root in order to smooth gradient
domains = sqrtDomains;

if (smooth) {
scale = smoothScale(ColorsArray, domains, space);
} else {
scale = d3.scaleLinear()
.range(ColorsArray)
.domain(domains)
.range(ColorsArray)
.interpolate(space.interpolator);
}

let Colors = d3.range(swatches).map(d => scale(d));
let Colors = [];

if(fullScale) {
// Colors = d3.range(swatches).map(d => scale(d));
let inc = 1 / (swatches - 1);
for(let i = 0; i < swatches; i++) {
let currentInc = inc * i;
let color = scale(currentInc * swatches);
Colors.push(color);
}
}
else if (!fullScale) {
let inc = 1 / (swatches - 1);
for(let i = 0; i < swatches; i++) {
let currentInc = inc * i;
let color = scale(currentInc * swatches);
Colors.push(color);
}
}

let colors = Colors.filter(el => el != null);

Expand Down Expand Up @@ -629,13 +677,55 @@ function binarySearch(list, value, baseLum) {
return (list[middle] == !value) ? closest : middle // how it was originally expressed
}


function generateSequentialColors(
{
swatches,
colorKeys,
colorspace = 'LAB',
shift = 1,
fullScale = true,
correctLightness = true,
smooth = false
} = {}) {

if (swatches === undefined) {
return function(swatches) {
// return generateAdaptiveTheme({baseScale: baseScale, colorScales: colorScales, brightness: brightness, contrast: contrast});
return generateSequentialColors({swatches: swatches, colorKeys: colorKeys, colorspace: colorspace, shift: shift, fullScale: fullScale, correctLightness: correctLightness, smooth: smooth});
}
}
else {
let sequenceData = createScale({swatches: swatches, colorKeys: colorKeys, colorspace: colorspace, shift: shift, fullScale: fullScale, correctLightness: correctLightness, smooth: smooth});
let colorRange = sequenceData.colors;

let fillDomain = [];
for(let i=0; i<swatches; i++) {
// fill domain from 0 to 1 with percentage values of the swatches
fillDomain.push(sequenceData.colors.length * (i / (swatches)));
}

let sequentialScale = d3.scaleLinear()
.range(sequenceData.colors)
.domain(fillDomain);

let newColors = d3.range(swatches).map(function(d) {
let c = sequentialScale(d);
return d3.rgb(c).formatHex();
});

return newColors;
}
}

export {
createScale,
luminance,
contrast,
binarySearch,
generateBaseScale,
generateContrastColors,
generateSequentialColors,
minPositive,
ratioName,
generateAdaptiveTheme
Expand Down
77 changes: 77 additions & 0 deletions packages/contrast-colors/test/generateSequentialColors.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
Copyright 2020 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the 'License');
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/

import { generateSequentialColors } from '../index.js';

test('should output same input colors', function() {
let colors = generateSequentialColors({
swatches: 3,
colorKeys: ['#FFB600', '#FF0049', '#1900BC'],
colorspace: 'RGB',
shift: 1,
fullScale: false,
correctLightness: false
});

expect(colors).toEqual(['#ffb600', '#ff0049', '#1900bc']);
});

test('should output 5 colors including same input colors', function() {
let colors = generateSequentialColors({
swatches: 5,
colorKeys: ['#FFB600', '#FF0049', '#1900BC'],
colorspace: 'LCH',
shift: 1,
fullScale: false,
correctLightness: false
});

expect(colors).toEqual(['#ffb600', '#ff7425', '#ff0049', '#c80089', '#1900bc']);
});

test('should output 5 corrected colors with first & last matching input colors', function() {
let colors = generateSequentialColors({
swatches: 5,
colorKeys: ['#FFB600', '#FF0049', '#1900BC'],
colorspace: 'LCH',
shift: 1,
fullScale: false,
correctLightness: true
});

expect(colors).toEqual(['#ffb600', '#ff6c29', '#fa0054', '#c0008f', '#1900bc']);
});

test('should output 10 corrected colors with first & last matching input colors', function() {
let colors = generateSequentialColors({
swatches: 10,
colorKeys: ['#FFB600', '#FF0049', '#1900BC'],
colorspace: 'LCH',
shift: 1,
fullScale: false,
correctLightness: true
});

expect(colors).toEqual(['#ffb600', '#ff970f', '#ff7524', '#ff4e37', '#ff0a48', '#f20061', '#da007b', '#b50095', '#8000ac', '#1900bc']);
});

test('should return a sequential function', function() {
let scale = generateSequentialColors({
colorKeys: ['#FFB600', '#FF0049', '#1900BC'],
colorspace: 'LCH',
shift: 1,
fullScale: false,
correctLightness: true
});
let colors = scale(5);

expect(colors).toEqual(['#ffb600', '#ff6c29', '#fa0054', '#c0008f', '#1900bc']);
});
5 changes: 4 additions & 1 deletion packages/ui/src/charts.js
Original file line number Diff line number Diff line change
Expand Up @@ -569,11 +569,14 @@ let chartColors = [];

function getChartColors(mode) {
let shift = document.getElementById('shiftInput').value;
let lightnessCorrectionInput = document.getElementById('opticalScale');
let correctLightness = lightnessCorrectionInput.checked;
let clamping = document.getElementById('sequentialClamp').checked;

let chartColors = [];

// GENERATE PROPER SCALE OF COLORS FOR 3d CHART:
let chartRGB = contrastColors.createScale({swatches: 340, colorKeys: colorArgs, colorspace: mode, shift: shift});
let chartRGB = contrastColors.createScale({swatches: 340, colorKeys: colorArgs, colorspace: mode, fullScale: clamping, shift: shift, correctLightness: correctLightness});

for (let i=0; i<chartRGB.colorsHex.length; i++) {
chartColors.push(chartRGB.colorsHex[i]);
Expand Down
25 changes: 21 additions & 4 deletions packages/ui/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,13 @@ <h3 class="spectrum-Heading6">Key Colors</h3>
<div id="colorSlider-wrapper"></div>
</div>
<div>
<form class="spectrum-Form" style="display:none;">
<form class="spectrum-Form">
<div class="spectrum-Form-item">
<label for="paletteType_Dropdown" class="spectrum-FieldLabel">Palette Type</label>
<div class="spectrum-Dropdown" id="paletteType_Dropdown">
<select class="spectrum-FieldButton spectrum-Dropdown-trigger" name="paletteType" onInput="changePalette()" id="paletteType">
<select class="spectrum-FieldButton spectrum-Dropdown-trigger" name="paletteType" onInput="changePaletteUpdate()" id="paletteType">
<option value="Contrast">Contrast ratio based</option>
<option value="Sequential">Sequential</option>
</select>
<svg xmlns:xlink="http://www.w3.org/1999/xlink" class="spectrum-Icon spectrum-UIIcon-ChevronDownMedium spectrum-Dropdown-icon">
<use xlink:href="#spectrum-css-icon-ChevronDownMedium"></use>
Expand All @@ -145,7 +147,7 @@ <h3 class="spectrum-Heading6">Key Colors</h3>
<label class="spectrum-Slider-label" id="shiftInputLabel" for="shiftInput">Shift scale</label>
<div class="spectrum-Slider-value" role="textbox" aria-readonly="true" aria-labelledby="shiftInputLabel" id="shiftInputValue">14</div>
</div>
<input class="spectrum-Slider-controls" type="range" value="1" min="0.25" max="1.5" step="0.01" id="shiftInput" name="shiftInput" onInput="colorInput()"></input>
<input class="spectrum-Slider-controls" type="range" value="1" min="0.25" max="2.5" step="0.01" id="shiftInput" name="shiftInput" onInput="colorInput()"></input>
</div>
</div>
<div class="spectrum-Form-item">
Expand All @@ -159,13 +161,28 @@ <h3 class="spectrum-Heading6">Key Colors</h3>
<use xlink:href="#spectrum-css-icon-CheckmarkSmall" />
</svg>
</span>
<span class="spectrum-Checkbox-label">Clamp to key colors</span>
<span class="spectrum-Checkbox-label">Full scale (white to black)</span>
</label>
</div>
</div>
<div class="spectrum-Form-itemField">
<div class="spectrum-FieldGroup spectrum-FieldGroup--vertical">
<label class="spectrum-Checkbox">
<input type="checkbox" class="spectrum-Checkbox-input" id="opticalScale" onInput="colorInput()">
<span class="spectrum-Checkbox-box">
<svg xmlns:xlink="http://www.w3.org/1999/xlink" class="spectrum-Icon spectrum-UIIcon-CheckmarkSmall spectrum-Checkbox-checkmark" focusable="false" aria-hidden="true">
<use xlink:href="#spectrum-css-icon-CheckmarkSmall" />
</svg>
</span>
<span class="spectrum-Checkbox-label">Correct lightness</span>
</label>
</div>
</div>

</div>
</form>
</div>

<div id="contrastConfigs">
<div>
<input type="color" class="" id="bgField" value="#ffffff"></input>
Expand Down
Loading