Add Comma separator in the Y-axis label in Dygraph - javascript

I have below Dygraph using the dygraphs package
library(dygraphs)
library(htmlwidgets)
library(zoo)
valueFormatter = "function formatValue(v) {
var suffixes = ['', 'K', 'M', 'G', 'T'];
if (v < 1000) return v;
var magnitude = Math.ceil(String(Math.floor(v)).length / 4-1);
if (magnitude > suffixes.length - 1)
magnitude = suffixes.length - 1;
return String(Math.round(v / Math.pow(10, magnitude * 3), 2)) +suffixes[magnitude]
}"
Data = zoo(matrix(c(10000, 1000000, 100000000, 50000), nc = 1), as.Date(c('2015-01-05', '2016-01-05', '2017-01-05', '2018-01-05'))); colnames(Data) = 'x'
dygraph(Data, main = "") %>% dySeries(c("x")) %>%
dyAxis("y", axisLabelFormatter = JS(valueFormatter),
valueFormatter = JS(valueFormatter),
rangePad = 20)
However in the label of Y-axis I want to bring Thousand seperator for the tick values e.g. instead of 30000K I want to have 3,0000K. Is there any way to achieve this.

Convert your number 30000K into thousand separated value 30,000K.
Regex to place comma after each 3rd digit
var regex = /\B(?=(\d{3})+(?!\d))/g;
var number = "200";
var thousandSeprator = "20000";
console.log(number.replace(regex,',')); // 200
console.log(thousandSeprator.replace(regex,',')); // 20,000
Updated valueFormatter function
valueFormatter = "function formatValue(v) {
var suffixes = ['', 'K', 'M', 'G', 'T'];
var regex = /\B(?=(\d{3})+(?!\d))/g;
if (v < 1000) return v.toString().replace(regex,',');
var magnitude = Math.ceil(String(Math.floor(v)).length / 4-1);
if (magnitude > suffixes.length - 1)
magnitude = suffixes.length - 1;
return v.toString().replace(regex,',') +suffixes[magnitude]
}"

Related

JavaScript Regex to find UOM in a string

I have a list of products that contains UOM in the product title. It needs automatically detect the UOM in the title by using Regex.
Expectations
Banana Yogurt 70ml returns ml
Fish Nuggets 200G returns g
Potato Wedges 200 G returns g
I have this function below
detectMetricUnit = (title) => {
let unit,
regex = new RegExp(/(?:\d)/mg),
measurement = title.match(regex) && title.match(regex)[0],
matches = measurement && title.split(measurement)[1];
if(matches) {
if(/millilitre|milliliter|ml/.test(matches.toLowerCase())){
unit = 'ml';
} else if(/litre|liter|l/.test(matches.toLowerCase())){
unit = 'l';
} else if (/kilogram|kg/.test(matches.toLowerCase())) {
unit = 'kg';
} else if (/gram|g/.test(matches.toLowerCase())) {
unit = 'g';
}
}
return unit;
}
However I have some problematic strings such as
Chocolate Drink 330ML X 24 matches 3 and return null UOM
which I am expecting to get ml.
Appreciate if someone could point out my mistake in my regex. How do I actually get the full integers and find the UOM attached next to it even with a space?
You may define a dictionary of possible UOMs you want to detect and then build a regex similar to
/(\d+(?:\.\d+)?)\s?(millilitre|milliliter|ml|litre|liter|l|kilogram|kg|gram|g)\b/i
See the regex demo. The (\d+(?:\.\d+)?) part will capture an integer or float value into Group 1, then \s? match an optional whitespace (change to \s* to match 0 or more whitespaces), and then (millilitre|milliliter|ml|litre|liter|l|kilogram|kg|gram|g)\b will capture UOM unit into Group 2 as a whole word (due to \b word boundary).
Here is the JS implementation to get the first UOM from string:
let strs = ['Banana Yogurt 70ml', 'Fish Nuggets 200G', 'Potato Wedges 200 G', 'Chocolate Drink 330ML X 24']
let dct = {millilitre: 'ml', milliliter: 'ml', ml: 'ml', litre:'l', liter: 'l', l: 'l', kilogram: 'kg', kg: 'kg', gram: 'g', g: 'g'}
detectMetricUnit = (title) => {
let unit, match, val,
regex = new RegExp("(\\d+(?:\\.\\d+)?)\\s?(" + Object.keys(dct).join("|") + ")\\b", "i");
match = title.match(regex);
if (match) {
val = match[1];
unit = dct[match[2].toLowerCase()]
}
return [val, unit];
}
strs.forEach(x => console.log(detectMetricUnit(x)) )
To get all of them, multiple occurrences:
let strs = ['Banana Yogurt 70ml and Fish Nuggets 200G', 'Potato Wedges 200 G and Chocolate Drink 330ML X 24']
let dct = {millilitre: 'ml', milliliter: 'ml', ml: 'ml', litre:'l', liter: 'l', l: 'l', kilogram: 'kg', kg: 'kg', gram: 'g', g: 'g'}
detectMetricUnit = (title) => {
let match, results = [],
regex = new RegExp("(\\d+(?:\\.\\d+)?)\\s?(" + Object.keys(dct).join("|") + ")\\b", "ig");
while (match=regex.exec(title)) {
results.push([ match[1], dct[match[2].toLowerCase()] ]);
}
return results;
}
strs.forEach(x => console.log(x, detectMetricUnit(x)) )

Highcharts + R: Show tooltip values in million/billion etc. automatically

Highcharter (R wrapper for displaying Highcharts plots in R) does automatically adjust axis labels depending on the values, e.g. 405 will show as 405 whereas 3'000'000 will show as 3M.
I want to reproduce this for the tooltips shown.
This article outlines how to do this in Highcharts directly. However, I am not able to reproduce this using Highcharter.
Related:
highcharts tooltip format millions billions
JS Fiddle: http://jsfiddle.net/BlackLabel/ynCKW/104/
What am I missing?
chart_data <- tibble(
date_var = c(seq(from = as.Date('2019-09-10'), to = as.Date('2019-09-15'), by = 1))
, value = c(2304, 50000, 678900, 98457, 124684, 249547)
)
hchart(chart_data, type = 'line', hcaes(x = date_var, y = value)) %>%
hc_tooltip(formatter = JS(
"function() {
var axis = this.series.yAxis;
return axis.defaultLabelFormatter.call({
axis: axis,
value: this.y
});
}"
))
IMPORTANTLY: It seems to work for some values but not all of them. For instance, with above data I get correct tooltip labels for the 2nd and the 3rd date but all else show the original number.
In the related SO question you provided a link to, the assumption was to short numbers that have zeros (0) at the end. Just use the previous code from #Paweł Fus' answer:
formatter: function() {
var ret = '',
multi,
axis = this.series.yAxis,
numericSymbols = ['k', 'M', 'G', 'T', 'P', 'E'],
i = numericSymbols.length;
while (i-- && ret === '') {
multi = Math.pow(1000, i + 1);
if (axis.tickInterval >= multi && numericSymbols[i] !== null) {
ret = Highcharts.numberFormat(this.y / multi, -1) + numericSymbols[i];
}
}
return ret;
}
jsFiddle: https://jsfiddle.net/BlackLabel/yrb7gzap
Best regards!

5-second count down timer

I have a simple question. I'm trying to add a a count down timer to my first ever project. I'm planning on putting the timer on the top of the page, and I want it to display the current time left starting from 5:00 to 0:00. I know how to do it using setInterval which could do something like time-- every second, but I want it to also display the current milliseconds and I'm not sure how to go about doing that. Any help would be very much appreciated!
This is the code I've written so far, and right now it just has a 5 second timeout used in the assignWords() method.
(function () {
'use strict';
// Dictionary object used to manipulate anagrams
var dictionary = {};
// List of letters used to help create incorrect choices
dictionary.letters = [
'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l',
'm', 'n', 'o', 'p', 'q', 'r',
's', 't', 'u', 'v', 'w', 'x',
'y', 'z'];
// List of words that are used for anagram questions
dictionary.words = [
'adaxial', 'agreeably', 'antinoise', 'asthenia', 'astint', 'babushka', 'bailiffry', 'bathtub', 'bestab', 'bestiary', 'bibulous', 'bordage', 'bostonite', 'brogue', 'brushoff', 'budlet', 'cathepsin', 'centesimi', 'chaste', 'chicayote', 'coastal', 'coppice', 'couple', 'cuapinole', 'cytoplasm', 'daubingly', 'dearth', 'deasil', 'drightin', 'drudge', 'ejecta', 'feelable', 'fistnote', 'flareback', 'folial', 'fortunate', 'garrulous', 'gemmology', 'glaringly', 'gleet', 'globule', 'gluepot', 'googol', 'googul', 'humuslike', 'ichnology', 'illiberal', 'issite', 'karyotin', 'kella', 'ketol', 'knowingly', 'lysogenic', 'macaque', 'meddle', 'menseful', 'mocha', 'mournival', 'musher', 'natty', 'nonactive', 'nonserous', 'outcut', 'outspeak', 'overheavy', 'partially', 'pernor', 'picnic', 'prickwood', 'pyorrheal', 'redly', 'refine', 'regaler', 'rollick', 'sandling', 'sarcastic', 'scypha', 'severely', 'sinkage', 'sissyish', 'sogging', 'staling', 'steellike', 'stonelike', 'stoneware', 'tadpolism', 'tarditude', 'tazia', 'thymiosis', 'tightener', 'tritical', 'trundler', 'undenuded', 'underbank', 'unpaining', 'untraded', 'wayfare', 'woodworm', 'woofer', 'zemeism'];
// Stores the count of the remaining words
dictionary.wordCount = dictionary.words.length;
/*
* Returns a random letter from dictionary.letters
*/
dictionary.randLetter = function () {
return this.letters[Math.floor(Math.random() * 100 % 26)];
};
/*
* Replaces one letter of a word with a randomly selected letter
*/
dictionary.replaceLetter = function (word) {
var index = Math.floor(Math.random() * 100 % word.length);
var newWord = word.slice(0, index) + word.slice(index + 1);
return newWord += this.randLetter();
};
/*
* Returns a random word from dictionary.words
*/
dictionary.randWord = function () {
return this.words[Math.floor(Math.random() * 100 % this.wordCount)];
};
/*
* Randomly shuffles the letters around in a word
*/
dictionary.shuffle = function (word) {
var fragments = word.split('');
for (var i = fragments.length; i > 0;) {
var random = parseInt(Math.random() * i);
var temp = fragments[--i];
fragments[i] = fragments[random];
fragments[random] = temp;
}
return fragments.join('');
};
/*
* Returns the correct answer for the current word
*/
dictionary.getCorrectChoice = function (word) {
return this.shuffle(word);
};
/*
* Returns an incorrect answer for the current word
*/
dictionary.getIncorrectChoice = function (word) {
word = this.replaceLetter(word);
return this.shuffle(word);
};
/*
* Randomly assigns the current word and correct and incorrect choices to the four buttons
*/
function assignWords() {
// Clear the timeout for the previous question
window.clearTimeout(dictionary.timeout);
// Allow 5 seconds for the user to get the right answer
dictionary.timeout = window.setTimeout(function() {
alert('you lose!');
}, 5000);
var currWord = document.getElementById('currentWord');
var buttons = document.getElementsByTagName('button');
// Randomly choose a word to use as the anagram test
currWord.innerHTML = dictionary.randWord();
// Randomly choose a button to hold the correct choice
dictionary.correctButton = buttons[Math.floor(Math.random() * 4)];
dictionary.correctButton.innerHTML = dictionary.getCorrectChoice(currWord.innerHTML);
// Give the rest of the buttons incorrect choices
for (var i = 0; i < buttons.length; i++) {
if (buttons[i] === dictionary.correctButton) {
continue;
} else {
buttons[i].innerHTML = dictionary.getIncorrectChoice(currWord.innerHTML);
}
}
}
// View object used to change information displayed on the page
var view = {};
// Stores the player's current score
view.score = 0;
// The timer that has the remaining time to answer the question
view.timer = 0;
//
view.resetData = function() {
};
view.displayData = function() {
assignWords();
var buttons = document.getElementsByTagName('button');
for(var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', function(event) {
if (event.target === dictionary.correctButton) {
assignWords();
}
});
}
};
view.displayData();
})();
Use setInterval with a small interval such as 50ms to update your timer display.
To get the time left, instead of decrementing a counter by 1 every second, just note the start time and subtract it from the current time. This will give you the total elapsed milliseconds since the timer started which you can then format for display.
Have found similar code on the W3C website and thought it might be what you are looking for:
var d = new Date();
var x = document.getElementById("demo");
var h = d.getHours();
var m = d.getMinutes();
var s = d.getSeconds();
var ms = d.getMilliseconds();
x.innerHTML = h + ":" + m + ":" + s + ":" + ms;

Sort Colour / Color Values

Im looking to align the following array of colours as precisely as possible.
After searching & trying many solutions suggested on Stackoverflow, the pusher.color library has the best solution, however, it too is far from perfect. I would like to hear solutions on how we can align them perfectly.
JSFIDDLE LINK : http://jsfiddle.net/dxux7y3e/
Code:
var coloursArray=['#FFE9E9','#B85958','#FFB1AE','#FFC2BF','#C55E58','#FFC7C4','#FF9A94','#FF9D96','#FA9790','#A78B88','#A78B88','#CE675B','#DB8073','#FF9D90','#FF7361','#FFD6D1','#F9A092','#FF7B67','#EBACA2','#FF806D','#DD6D5B','#D16654','#ED8673','#FFC4B8','#E2725B','#ED7A64','#8F3926','#BD492F','#9D3C27','#AD533E','#BF4024','#FFC9BC','#6B6766','#E1CDC8','#C2654C','#B3978F','#FFC7B8','#CE2B00','#C2654C','#A24D34','#FF926D','#E78667','#FFB198','#8C756D','#9E6D5B','#FFC7B0','#FFBEA4','#D2B9AF','#FFB193','#632710','#B26746','#976854','#F44900','#E79873','#EFA27F','#532510','#BC866B','#FDE5D9','#FF5B00','#D18C67','#FF5B00','#9E4312','#763713','#BB6B39','#B5622E','#CC7742','#6D4227','#B56B38','#FF7518','#F3B080','#995C30','#995C30','#FF6A00','#D89769','#71472A','#EDAC7B','#EEAB79','#EBCFB9','#FBE3D1','#E19255','#5E381B','#FFDCC1','#FFF0E4','#F68D39','#7B5B40','#FF8313','#FFCEA4','#AA8667','#975414','#CB9867','#8C5B2B','#FFCE9E','#7B4714','#FFF3E7','#FFA449','#CEAF90','#CDB69E','#EFD6BC','#DDA66B','#B27737','#B88A57','#CE9B61','#F4C38B','#543817','#BC9C78','#DBB07A','#FF8E04','#F6EADB','#DBC2A4','#C49B64','#CBA26B','#80551E','#FF9200','#FFECD3','#FFC87C','#FFB755','#DBB680','#D2D0CD','#EFDBBE','#E5C18B','#FFE5BC','#F2EADB','#885F12','#FFE7B6','#825A08','#906712','#F2D18E','#C8C6C2','#FFB000','#FFC243','#C6BEAD','#D0C3A4','#916800','#8C6700','#F4E9CA','#FFF0C5','#FFE080','#FFEBA8','#846600','#FFE692','#F5F0DB','#433F2F','#BBB394','#FFEFAA','#FFE76D','#FFFAE0','#3E3B28','#554900','#E1E0D8','#74725C','#605F54','#F8F7DD','#A5A467','#DDDDDA','#FFFFEE','#A3A39D','#E0E0D7','#BEBEB9','#E8E8E5','#454531','#ACACAA','#E9E9DF','#FFFFDC','#EBEBE7','#979831','#C5C6BE','#B9C866','#898D72','#F3FAD1','#616452','#CED5B0','#A1A787','#595C4E','#B0BB8C','#EEFFB6','#ACB78E','#8FA359','#858F6C','#86916E','#374912','#AEB0AA','#79904C','#627739','#747F60','#9FA98E','#E7F9CB','#E1F9BE','#495637','#8A9978','#4E5F39','#86996E','#C3CEB7','#78866B','#CEDDC1','#B5CEA2','#536149','#D6E6CC','#D6E6CC','#809873','#4F564C','#4F6C45','#555F52','#4F7942','#5F705B','#D0DFCD','#2B3929','#F0F7EF','#AAD5A4','#99BC95','#B6D4B4','#869E86','#618661','#006700','#E9EEE9','#739E73','#005B06','#EDF7EE','#D0E0D2','#809784','#ABCEB1','#C0E0C8','#3A5241','#435549','#E6ECE8','#E3EAE6','#3B604C','#00602F','#92B7A5','#2F5B49','#318061','#30745B','#316955','#00A275','#C2D1CE','#80A7A0','#00A082','#C2D1CF','#5C6E6C','#607473','#EDF7F7','#1E8285','#D5E7E8','#AADEE1','#188086','#107F87','#566364','#007B86','#66949A','#CAE2E5','#18656F','#004F61','#0C5B6C','#668E98','#BBD0DA','#91B4C5','#AFC3CD','#738A99','#3A5467','#476174','#244967','#556C80','#667A8C','#516D87','#1E4263','#7C8791','#849CB6','#738CAA','#1E3A5F','#1E3655','#9EB0CE','#B6BAC2','#67738D','#BEC1CD','#555559','#616180','#000049','#000031','#F8F8FC','#938BA4','#47375D','#F7F6F8','#3D0067','#514C53','#9566A2','#7F5482','#A279A4','#6D1261','#A06492','#925582','#945B80','#CE94BA','#ECCFE1','#A20058','#A6005B','#BC0061','#BB0061','#F3CEE1','#B3005B','#AB165F','#8A184D','#AA185B','#F3DAE4','#DB3779','#E71261','#E74F86','#FFD6E5','#BE9BA7','#D0396A','#DB1855','#F798B6','#9C294A','#D62B5B','#DE3969','#BC1641','#E7547A','#D52756','#9C7D85','#DB244F','#A1354F','#C22443','#FFBDCA','#8B6D73','#DC3D5B','#FF738C','#F13154','#BC4055','#FED4DB','#FFCFD6','#CB4E61','#ED455A','#F36C7B','#C94F5B','#F3959D','#A8444C','#FFCCD0','#735B5D','#D15D67','#B44B52','#FD868D','#FFD5D8','#C3767B','#FF8087','#C8242B','#FFEAEB','#F95A61','#E96D73','#E6656B','#FF6D73','#FF555B','#A35A5B','#FFD3D4','#B84B4D'];
var body=document.getElementsByTagName('body')[0];
function hexToRgb(hex) {
hex = hex.substring(1, hex.length);
var r = parseInt((hex).substring(0, 2), 16);
var g = parseInt((hex).substring(2, 4), 16);
var b = parseInt((hex).substring(4, 6), 16);
return r + "," + g + "," + b;
}
function rgbToHex(r, g, b) {
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
var rgbArr=new Array();
var div=document.createElement('div');
div.id='Original';
body.appendChild(div);
for(var color in coloursArray){
color=coloursArray[color];
displayColor(color,div);
rgbArr.push(hexToRgb(color));
}
var hslArr=new Array();
for(var i=0;i<rgbArr.length;i++){
//Transforming rgb to hsl
//`hslArr[i][1]` (`i`) is a reference to the rgb color, in order to retrieve it later
hslArr[i]=[rgbToHsl(rgbArr[i]),i];
}
var sortedHslArr=new Array();
//Sorting `hslArr` into `sortedHslArr`
outerloop:
for(var i=0;i<hslArr.length;i++){
for(var j=0;j<sortedHslArr.length;j++){
if(sortedHslArr[j][0][0]>hslArr[i][0][0]){
sortedHslArr.splice(j,0,hslArr[i]);
continue outerloop;
}
}
sortedHslArr.push(hslArr[i]);
}
var sortedRgbArr=new Array();
//Retrieving rgb colors
for(var i=0;i<sortedHslArr.length;i++){
sortedRgbArr[i]=rgbArr[sortedHslArr[i][1]];
}
function displayColor(color,parent){
var div;
div=document.createElement('div');
div.style.backgroundColor=color;
div.style.width='22px';
div.style.height='22px';
div.style.cssFloat='left';
div.style.position='relative';
parent.appendChild(div);
}
var finalArray=new Array();
var div=document.createElement('div');
div.id='Sorted';
body.appendChild(div);
for(var color in sortedRgbArr){
color=sortedRgbArr[color];
color=color.split(',');
color=rgbToHex(parseInt(color[0]),parseInt(color[1]),parseInt(color[2]));
displayColor(color,div);
finalArray.push(color);
}
function rgbToHsl(c){
var r = c[0]/255, g = c[1]/255, b = c[2]/255;
var max = Math.max(r, g, b), min = Math.min(r, g, b);
var h, s, l = (max + min) / 2;
if(max == min){
h = s = 0; // achromatic
}else{
var d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch(max){
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return new Array(h * 360, s * 100, l * 100);
}
var sorted = coloursArray.sort(function(colorA, colorB) {
return pusher.color(colorA).hue() - pusher.color(colorB).hue();
});
// console.log(sorted);
var div=document.createElement('div');
div.id='Pusher';
body.appendChild(div);
for(var color in sorted){
color=sorted[color];
displayColor(color,div);
}
var div=document.createElement('div');
body.appendChild(div);
var str='';
for(var color in sorted){
color=sorted[color];
str+='\''+color+'\',';
}
div.innerHTML=str;
function sorthueColors (colors) {
for (var c = 0; c < colors.length; c++) {
/* Get the hex value without hash symbol. */
var hex = colors[c].substring(1);
//var hex = colors[c].hex.substring(1);
/* Get the RGB values to calculate the Hue. */
var r = parseInt(hex.substring(0,2),16)/255;
var g = parseInt(hex.substring(2,4),16)/255;
var b = parseInt(hex.substring(4,6),16)/255;
/* Getting the Max and Min values for Chroma. */
var max = Math.max.apply(Math, [r,g,b]);
var min = Math.min.apply(Math, [r,g,b]);
/* Variables for HSV value of hex color. */
var chr = max-min;
var hue = 0;
var val = max;
var sat = 0;
if (val > 0) {
/* Calculate Saturation only if Value isn't 0. */
sat = chr/val;
if (sat > 0) {
if (r == max) {
hue = 60*(((g-min)-(b-min))/chr);
if (hue < 0) {hue += 360;}
} else if (g == max) {
hue = 120+60*(((b-min)-(r-min))/chr);
} else if (b == max) {
hue = 240+60*(((r-min)-(g-min))/chr);
}
}
}
/* Modifies existing objects by adding HSV values. */
colors[c].hue = hue;
colors[c].sat = sat;
colors[c].val = val;
}
/* Sort by Hue. */
return colors.sort(function(a,b){return a.hue - b.hue;});
}
Problem is, sorting requires a well-defined order - in other words, you need to map all colors to one dimension. While there are some approaches to display the color space in two dimensions, I am not aware of any that would display it in one dimension and still make sense to the human eye.
However, if you don't insist on some universal ordering and you merely want to place a given list of colors in a way that looks nice, then some clustering approach might produce better results. I tried the naive approach, the idea here is to put similar colors into the same cluster and to merge these clusters until you only have one. Here is the code I've got:
function colorDistance(color1, color2) {
// This is actually the square of the distance but
// this doesn't matter for sorting.
var result = 0;
for (var i = 0; i < color1.length; i++)
result += (color1[i] - color2[i]) * (color1[i] - color2[i]);
return result;
}
function sortColors(colors) {
// Calculate distance between each color
var distances = [];
for (var i = 0; i < colors.length; i++) {
distances[i] = [];
for (var j = 0; j < i; j++)
distances.push([colors[i], colors[j], colorDistance(colors[i], colors[j])]);
}
distances.sort(function(a, b) {
return a[2] - b[2];
});
// Put each color into separate cluster initially
var colorToCluster = {};
for (var i = 0; i < colors.length; i++)
colorToCluster[colors[i]] = [colors[i]];
// Merge clusters, starting with lowest distances
var lastCluster;
for (var i = 0; i < distances.length; i++) {
var color1 = distances[i][0];
var color2 = distances[i][1];
var cluster1 = colorToCluster[color1];
var cluster2 = colorToCluster[color2];
if (!cluster1 || !cluster2 || cluster1 == cluster2)
continue;
// Make sure color1 is at the end of its cluster and
// color2 at the beginning.
if (color1 != cluster1[cluster1.length - 1])
cluster1.reverse();
if (color2 != cluster2[0])
cluster2.reverse();
// Merge cluster2 into cluster1
cluster1.push.apply(cluster1, cluster2);
delete colorToCluster[color1];
delete colorToCluster[color2];
colorToCluster[cluster1[0]] = cluster1;
colorToCluster[cluster1[cluster1.length - 1]] = cluster1;
lastCluster = cluster1;
}
// By now all colors should be in one cluster
return lastCluster;
}
Complete fiddle
The colorDistance() function works with RGB colors that are expressed as arrays with three elements. There are certainly much better approaches to do this and they might produce results that look better. Note also that this isn't the most efficient algorithm because it calculates the distance between each and every color (O(n2)) so if you have lots of colors you might want to optimize it.
Assuming there is no best solution. Here is how i would find what i like the most.
First i would use color distance function:
var balance = [10, 0, 0.01];
function colorDistance(color1, color2) {
var result = 0;
color1 = rgbToHsl(color1[0], color1[1], color1[2]);
color2 = rgbToHsl(color2[0], color2[1], color2[2]);
for (var i = 0; i < color1.length; i++)
result += (color1[i] - color2[i]) * (color1[i] - color2[i]) * balance[i];
return result;
}
I would use balance function :
hue distance var balance = [1, 0, 0];
saturation distance var balance = [0, 1, 0];
lightness distance var balance = [0, 0, 1];
After that you can just experiment and pick what you like. Note that values are not normalized to appear between 0 to 1 but other then that it should work well. You can also you another distance function this is just most trivial and well known one that gets decent results.
This approach does not give you the best solution, but describes a way to fiddle with it and get result you where looking for. Have fun.
Using the Hue of a color is usually a pleasant way to map the color on a one-dimensional scale. You could convert the rgb color to hsv using the answers here: RGB to HSV color in javascript?
This is an improved code based on pusher.color lib.
https://jsfiddle.net/kt1zv6g4/3/

Javascript color gradient

Using javascript with or without Jquery, I need to a create a gradient of colours based on a start and finish color. Is this possible to do programmatically?
The end colour is only ever going to be darker shade of the start colour and it's for an unordered list which I have no control over the number of li items. I'm looking for a solution that allows me to pick a start and end color, convert the hex value into RGB so it can be manipulated in code. The starting RGB values gets incremented by a step value calculated based upon the number of items.
so if the list had 8 items then the it needs to increment the seperate Red Green Blue values in 8 steps to achieve the final colour. Is there a better way to do it and if so where can I find some sample code?
I created a JS library, RainbowVis-JS to solve this general problem. You just have to set the number of items using setNumberRange and set the start and end colour using setSpectrum. Then you get the hex colour code with colourAt.
var numberOfItems = 8;
var rainbow = new Rainbow();
rainbow.setNumberRange(1, numberOfItems);
rainbow.setSpectrum('red', 'black');
var s = '';
for (var i = 1; i <= numberOfItems; i++) {
var hexColour = rainbow.colourAt(i);
s += '#' + hexColour + ', ';
}
document.write(s);
// gives:
// #ff0000, #db0000, #b60000, #920000, #6d0000, #490000, #240000, #000000,
You are welcome to look at the library's source code. :)
Correct function to generate array of colors!
function hex (c) {
var s = "0123456789abcdef";
var i = parseInt (c);
if (i == 0 || isNaN (c))
return "00";
i = Math.round (Math.min (Math.max (0, i), 255));
return s.charAt ((i - i % 16) / 16) + s.charAt (i % 16);
}
/* Convert an RGB triplet to a hex string */
function convertToHex (rgb) {
return hex(rgb[0]) + hex(rgb[1]) + hex(rgb[2]);
}
/* Remove '#' in color hex string */
function trim (s) { return (s.charAt(0) == '#') ? s.substring(1, 7) : s }
/* Convert a hex string to an RGB triplet */
function convertToRGB (hex) {
var color = [];
color[0] = parseInt ((trim(hex)).substring (0, 2), 16);
color[1] = parseInt ((trim(hex)).substring (2, 4), 16);
color[2] = parseInt ((trim(hex)).substring (4, 6), 16);
return color;
}
function generateColor(colorStart,colorEnd,colorCount){
// The beginning of your gradient
var start = convertToRGB (colorStart);
// The end of your gradient
var end = convertToRGB (colorEnd);
// The number of colors to compute
var len = colorCount;
//Alpha blending amount
var alpha = 0.0;
var saida = [];
for (i = 0; i < len; i++) {
var c = [];
alpha += (1.0/len);
c[0] = start[0] * alpha + (1 - alpha) * end[0];
c[1] = start[1] * alpha + (1 - alpha) * end[1];
c[2] = start[2] * alpha + (1 - alpha) * end[2];
saida.push(convertToHex (c));
}
return saida;
}
// Exemplo de como usar
var tmp = generateColor('#000000','#ff0ff0',10);
for (cor in tmp) {
$('#result_show').append("<div style='padding:8px;color:#FFF;background-color:#"+tmp[cor]+"'>COLOR "+cor+"° - #"+tmp[cor]+"</div>")
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="result_show"></div>
Yes, absolutely.
I do this in Java, should be fairly simple to do in JavaScript too.
First, you'll need to break the colors up into RGB components.
Then calculate the differences between start and finish of the components.
Finally, calculate percentage difference and multiply by the start color of each component, then add it to the start color.
Assuming you can get the RGB values, this should do it:
var diffRed = endColor.red - startColor.red;
var diffGreen = endColor.green - startColor.green;
var diffBlue = endColor.blue - startColor.blue;
diffRed = (diffRed * percentFade) + startColor.red;
diffGreen = (diffGreen * percentFade) + startColor.green;
diffBlue = (diffBlue * percentFade) + startColor.blue;
The "percentFade" is a floating decimal, signifying how far to fade into the "endColor". 1 would be a full fade (thus creating the end color). 0 would be no fade (the starting color).
I use this function based on #desau answer:
getGradientColor = function(start_color, end_color, percent) {
// strip the leading # if it's there
start_color = start_color.replace(/^\s*#|\s*$/g, '');
end_color = end_color.replace(/^\s*#|\s*$/g, '');
// convert 3 char codes --> 6, e.g. `E0F` --> `EE00FF`
if(start_color.length == 3){
start_color = start_color.replace(/(.)/g, '$1$1');
}
if(end_color.length == 3){
end_color = end_color.replace(/(.)/g, '$1$1');
}
// get colors
var start_red = parseInt(start_color.substr(0, 2), 16),
start_green = parseInt(start_color.substr(2, 2), 16),
start_blue = parseInt(start_color.substr(4, 2), 16);
var end_red = parseInt(end_color.substr(0, 2), 16),
end_green = parseInt(end_color.substr(2, 2), 16),
end_blue = parseInt(end_color.substr(4, 2), 16);
// calculate new color
var diff_red = end_red - start_red;
var diff_green = end_green - start_green;
var diff_blue = end_blue - start_blue;
diff_red = ( (diff_red * percent) + start_red ).toString(16).split('.')[0];
diff_green = ( (diff_green * percent) + start_green ).toString(16).split('.')[0];
diff_blue = ( (diff_blue * percent) + start_blue ).toString(16).split('.')[0];
// ensure 2 digits by color
if( diff_red.length == 1 ) diff_red = '0' + diff_red
if( diff_green.length == 1 ) diff_green = '0' + diff_green
if( diff_blue.length == 1 ) diff_blue = '0' + diff_blue
return '#' + diff_red + diff_green + diff_blue;
};
Example:
getGradientColor('#FF0000', '#00FF00', 0.4);
=> "#996600"
desau's answer is great. Here it is in javascript:
function hexToRgb(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
function map(value, fromSource, toSource, fromTarget, toTarget) {
return (value - fromSource) / (toSource - fromSource) * (toTarget - fromTarget) + fromTarget;
}
function getColour(startColour, endColour, min, max, value) {
var startRGB = hexToRgb(startColour);
var endRGB = hexToRgb(endColour);
var percentFade = map(value, min, max, 0, 1);
var diffRed = endRGB.r - startRGB.r;
var diffGreen = endRGB.g - startRGB.g;
var diffBlue = endRGB.b - startRGB.b;
diffRed = (diffRed * percentFade) + startRGB.r;
diffGreen = (diffGreen * percentFade) + startRGB.g;
diffBlue = (diffBlue * percentFade) + startRGB.b;
var result = "rgb(" + Math.round(diffRed) + ", " + Math.round(diffGreen) + ", " + Math.round(diffBlue) + ")";
return result;
}
function changeBackgroundColour() {
var count = 0;
window.setInterval(function() {
count = (count + 1) % 200;
var newColour = getColour("#00FF00", "#FF0000", 0, 200, count);
document.body.style.backgroundColor = newColour;
}, 20);
}
changeBackgroundColour();
There is a JavaScript library which can create color gradients:
javascript-color-gradient
import Gradient from "javascript-color-gradient";
const colorGradient = new Gradient();
colorGradient.setGradient("#e6062d", "#408247"); // from red to green
colorGradient.setMidpoint(8); // set to 8 color steps
colorGradient.getArray(); // get all 8 colors: [ "#d11630", "#bd2534", ... ]
colorGradient.getColor(1); // #bd2534
Based on #drinor's answer - TypeScript support
const getGradientColor = (startColor: string, endColor: string, percent: number) => {
// strip the leading # if it's there
startColor = startColor.replace(/^\s*#|\s*$/g, '');
endColor = endColor.replace(/^\s*#|\s*$/g, '');
// convert 3 char codes --> 6, e.g. `E0F` --> `EE00FF`
if (startColor.length === 3) {
startColor = startColor.replace(/(.)/g, '$1$1');
}
if (endColor.length === 3) {
endColor = endColor.replace(/(.)/g, '$1$1');
}
// get colors
const startRed = parseInt(startColor.substr(0, 2), 16),
startGreen = parseInt(startColor.substr(2, 2), 16),
startBlue = parseInt(startColor.substr(4, 2), 16);
const endRed = parseInt(endColor.substr(0, 2), 16),
endGreen = parseInt(endColor.substr(2, 2), 16),
endBlue = parseInt(endColor.substr(4, 2), 16);
// calculate new color
let diffRed = endRed - startRed;
let diffGreen = endGreen - startGreen;
let diffBlue = endBlue - startBlue;
diffRed = ((diffRed * percent) + startRed);
diffGreen = ((diffGreen * percent) + startGreen);
diffBlue = ((diffBlue * percent) + startBlue);
let diffRedStr = diffRed.toString(16).split('.')[0];
let diffGreenStr = diffGreen.toString(16).split('.')[0];
let diffBlueStr = diffBlue.toString(16).split('.')[0];
// ensure 2 digits by color
if (diffRedStr.length === 1) diffRedStr = '0' + diffRedStr;
if (diffGreenStr.length === 1) diffGreenStr = '0' + diffGreenStr;
if (diffBlueStr.length === 1) diffBlueStr = '0' + diffBlueStr;
return '#' + diffRedStr + diffGreenStr + diffBlueStr;
}
example:
getGradientColor('#FF0000', '#00FF00', 0.4);
=> "#996600"
The xolor library has a gradient function. This will create an array with 8 colors in a gradient from a start color to an end color:
var gradientColors = []
var startColor = "rgb(100,200,50)", endColor = "green"
var start = xolor(startColor)
for(var n=0; n<8; n++) {
gradientColors.push(start.gradient(endColor, n/8))
}
See more on github: https://github.com/fresheneesz/xolor
chroma.js:
chroma.scale(['#fafa6e','#2A4858']).mode('lch').colors(6)
Not such mighty but in most cases working and you do not have to include any other libraries except jQuery for the following code:
HTML:
<div id="colors"></div>
JavaScript:
function rainbow(value, s, l, max, min, start, end) {
value = ((value - min) * (start - end) / max)+end;
return 'hsl(' + value + ','+s+'%,'+l+'%)';
}
function createRainbowDiv(start,end){
var gradient = $("<div>").css({display:"flex", "flex-direction":"row",height:"100%"});
for (var i = start; ((i <= end) && (i >= start)) || ((i >= end) && (i <= start));
i += (end-start) / Math.abs(end-start)){
gradient.append($("<div>").css({float:"left","background-color":rainbow(i, 100,50, Math.max(start,end), Math.min(start,end), start,end),flex:1}));
}
return gradient;
}
$("#colors").append(createRainbowDiv(0,150));
$("#colors").css("width","100%").css("height","10px");
This should make an div that contains a rainbow. See http://jsfiddle.net/rootandy/54rV7/
I needed to create a large enough array of color options for an unknown set of dynamic elements, but I needed each element to increment their way through a beginning color and an ending color. This sort of follows the "percent fade" approach except I had a difficult time following that logic. This is how I approached it using inputs of two rgb color values and calculating the number of elements on the page.
Here is a link to a codepen that demonstrates the concept.
Below is a code snippet of the problem.
<style>
#test {
width:200px;
height:100px;
border:solid 1px #000;
}
.test {
width:49%;
height:100px;
border:solid 1px #000;
display: inline-block;
}
</style>
</head>
<body>
<div id="test"></div>
<div class="test"></div>
<div class="test"></div>
<div class="test"></div>
<div class="test"></div>
<div class="test"></div>
<div class="test"></div>
<div class="test"></div>
<div class="test"></div>
<div class="test"></div>
<div class="test"></div>
<script>
var GColor = function(r,g,b) {
r = (typeof r === 'undefined')?0:r;
g = (typeof g === 'undefined')?0:g;
b = (typeof b === 'undefined')?0:b;
return {r:r, g:g, b:b};
};
// increases each channel by the difference of the two
// divided by 255 (the number of colors stored in the range array)
// but only stores a whole number
// This should respect any rgb combinations
// for start and end colors
var createColorRange = function(c1) {
var colorList = [], tmpColor, rr = 0, gg = 0, bb = 0;
for (var i=0; i<255; i++) {
tmpColor = new GColor();
if (rExp >= 0) {
tmpColor.r = Math.floor(c1.r - rr);
rr += rAdditive;
} else {
tmpColor.r = Math.floor(c1.r + rr);
rr += rAdditive;
}
if (gExp >= 0) {
tmpColor.g = Math.floor(c1.g - gg);
gg += gAdditive;
} else {
tmpColor.g = Math.floor(c1.g + gg);
gg += gAdditive;
}
if (bExp >= 0) {
tmpColor.b = Math.floor(c1.b - bb);
bb += bAdditive;
} else {
tmpColor.b = Math.floor(c1.b + bb);
bb += bAdditive;
}
console.log(tmpColor);
colorList.push(tmpColor);
}
return colorList;
};
/* ==================
Testing Code Below
================== */
var firstColor = new GColor(255, 24, 0);
var secondColor = new GColor(255, 182, 0);
// Determine the difference
var rExp = firstColor.r - secondColor.r;
// Divide that difference by length of the array
// you would like to create (255 in this case)
var rAdditive = Math.abs(rExp)/255;
var gExp = firstColor.g - secondColor.g;
var gAdditive = Math.abs(gExp)/255;
var bExp = firstColor.b - secondColor.b;
var bAdditive = Math.abs(bExp)/255;
var range = createColorRange(firstColor, secondColor);
console.log(range);
var pointer = 0;
// This gently cycles through
// all the colors on a single element
function rotateColors() {
var currentColor = range[pointer];
document.getElementById("test").style.backgroundColor = "rgb("+currentColor.r+","+currentColor.g+","+currentColor.b+")";
pointer++;
if (pointer < range.length) window.setTimeout(rotateColors, 5);
}
rotateColors();
// say I have 5 elements
// so I need 5 colors
// I already have my first and last colors
// but I need to locate the colors between
// my start color and my end color
// inside of this range
// so I divide the range's length by the
// number of colors I need
// and I store the index values of the middle values
// those index numbers will then act as my keys to retrieve those values
// and apply them to my element
var myColors = {};
var objects = document.querySelectorAll('.test');
myColors.num = objects.length;
var determineColors = function(numOfColors, colorArray) {
var colors = numOfColors;
var cRange = colorArray;
var distance = Math.floor(cRange.length/colors);
var object = document.querySelectorAll('.test');
var j = 0;
for (var i = 0; i < 255; i += distance) {
if ( (i === (distance*colors)) ) {
object[j].style.backgroundColor = "rgb(" + range[255].r + ", " + range[255].g + ", " + range[255].b + ")";
j = 0;
// console.log(range[i]);
} else {
// Apply to color to the element
object[j].style.backgroundColor = "rgb(" + range[i].r + ", " + range[i].g + ", " + range[i].b + ")";
// Have each element bleed into the next with a gradient
// object[j].style.background = "linear-gradient( 90deg, rgb(" + range[i].r + ", " + range[i].g + ", " + range[i].b + "), rgb(" + range[i+distance].r + ", " + range[i+distance].g + ", " + range[i+distance].b + "))";
j++;
}
}
};
setTimeout( determineColors(myColors.num, range), 2000);
</script>
</body>
You can retrieve the list of elements. I'm not familiar with jQuery, but prototypejs has Element.childElements() which will return an array. Once you know the length of the array, you can determine how much to change the pixel components for each step. Some of the following code I haven't tested out in the form I'm presenting it in, but it should hopefully give you an idea.
function hex (c) {
var s = "0123456789abcdef";
var i = parseInt (c);
if (i == 0 || isNaN (c))
return "00";
i = Math.round (Math.min (Math.max (0, i), 255));
return s.charAt ((i - i % 16) / 16) + s.charAt (i % 16);
}
/* Convert an RGB triplet to a hex string */
function convertToHex (rgb) {
return hex(rgb[0]) + hex(rgb[1]) + hex(rgb[2]);
}
/* Remove '#' in color hex string */
function trim (s) { return (s.charAt(0) == '#') ? s.substring(1, 7) : s }
/* Convert a hex string to an RGB triplet */
function convertToRGB (hex) {
var color[];
color[0] = parseInt ((trim(hex)).substring (0, 2), 16);
color[1] = parseInt ((trim(hex)).substring (2, 4), 16);
color[2] = parseInt ((trim(hex)).substring (4, 6), 16);
}
/* The start of your code. */
var start = convertToRGB ('#000000'); /* The beginning of your gradient */
var end = convertToRGB ('#ffffff'); /* The end of your gradient */
var arr = $('.gradientList').childElements();
var len = arr.length(); /* The number of colors to compute */
var alpha = 0.5; /* Alpha blending amount */
for (i = 0; i < len; i++) {
var c = [];
c[0] = start[0] * alpha + (1 - alpha) * end[0];
c[1] = start[1] * alpha + (1 - alpha) * end[1];
c[2] = start[2] * alpha + (1 - alpha) * end[2];
/* Set the background color of this element */
arr[i].setStyle ({ 'background-color': convertToHex (c) });
}
Basic Javascript - Background Gradient
Here's a ready-made function to set an elements background to be a gradient
Using CSS
Element.prototype.setGradient = function( from, to, vertical ){
this.style.background = 'linear-gradient(to '+(vertical ? 'top' : 'left')+', '+from+', '+to+' 100%)';
}
And Usage :
document.querySelector('.mydiv').setGradient('red','green');
This was tested working with chrome, I'll try to update for other browsers
Using Canvas
The most basic horizontal would be :
Element.prototype.setGradient = function( fromColor, toColor ){
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
var b = this.getBoundingClientRect();
var grd = ctx.createLinearGradient(0, 0, b.width, 0);
canvas.width = b.width;
canvas.height = b.height;
grd.addColorStop(0, fromColor);
grd.addColorStop(1, toColor);
ctx.fillStyle = grd;
ctx.fillRect(0, 0, b.width, b.height);
this.style.backgroundImage = 'url('+canvas.toDataURL()+')';
}
And Usage :
document.querySelector('.mydiv').setGradient('red','green');
A Fiddle :
https://jsfiddle.net/jch39bey/
-
Adding Vertical Gradient
A simple flag to set vertical
Element.prototype.setGradient = function( fromColor, toColor, vertical ){
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
var b = this.getBoundingClientRect();
var grd = ctx.createLinearGradient(0, 0, vertical ? 0 : b.width, vertical ? b.height : 0);
canvas.width = b.width;
canvas.height = b.height;
grd.addColorStop(0, fromColor);
grd.addColorStop(1, toColor);
ctx.fillStyle = grd;
ctx.fillRect(0, 0, b.width, b.height);
this.style.backgroundImage = 'url('+canvas.toDataURL()+')';
}
And Usage :
document.querySelector('.mydiv').setGradient('red','green',true);
Based on #desau's answer and some code from elsewhere, here's a jQuery step-by-step walkthrough:
function coloursBetween(fromColour, toColour, numberOfColours){
var colours = []; //holds output
var fromSplit = getRGBAValues(hexToRGBA(fromColour, 1.0)); //get raw values from hex
var toSplit = getRGBAValues(hexToRGBA(toColour, 1.0));
var fromRed = fromSplit[0]; //the red value as integer
var fromGreen = fromSplit[1];
var fromBlue = fromSplit[2];
var toRed = toSplit[0];
var toGreen = toSplit[1];
var toBlue = toSplit[2];
var difRed = toRed - fromRed; //difference between the two
var difGreen = toGreen - fromGreen;
var difBlue = toBlue - fromBlue;
var incrementPercentage = 1 / (numberOfColours-1); //how much to increment percentage by
for (var n = 0; n < numberOfColours; n++){
var percentage = n * incrementPercentage; //calculate percentage
var red = (difRed * percentage + fromRed).toFixed(0); //round em for legibility
var green = (difGreen * percentage + fromGreen).toFixed(0);
var blue = (difBlue * percentage + fromBlue).toFixed(0);
var colour = 'rgba(' + red + ',' + green + ',' + blue + ',1)'; //create string literal
colours.push(colour); //push home
}
return colours;
}
function getRGBAValues(string) {
var cleaned = string.substring(string.indexOf('(') +1, string.length-1);
var split = cleaned.split(",");
var intValues = [];
for(var index in split){
intValues.push(parseInt(split[index]));
}
return intValues;
}
function hexToRGBA(hex, alpha){
var c;
if(/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)){
c= hex.substring(1).split('');
if(c.length== 3){
c= [c[0], c[0], c[1], c[1], c[2], c[2]];
}
c= '0x'+c.join('');
return 'rgba('+[(c>>16)&255, (c>>8)&255, c&255].join(',')+','+alpha+')';
}
return rgba(0,0,0,1);
//throw new Error('Bad Hex');
}
There are three functions:
coloursBetween(fromColour, toColour, numberOfColours)
getRGBAValues(string)
hexToRGBA(hex, alpha)
Call the main function coloursBetween() passing in the starting colour and the ending colour, as well as the total number of colours you want to have returned. So if you request ten colours returned, you get the first from colour + 8 gradient colours + the final to colour.
The coloursBetween function starts by converting the incoming hex colours (e.g. #FFFFFF, #000000) into rgba (e.g. rgba(255,255,255,1) rgba(0,0,0,1)) and then subtracting the Red, Green and Blue values from each.
The difference between the Reds, Greens and Blues is then calculated. In this example it's -255 in each case. An increment is calculated and used to multiply new incremental values for the Red, Green and Blue. Alpha is always assumed to be one (full opacity). The new value is then added to the colours array and after the for loop has completed, it's returned.
Finally, call like this (going from Red to Blue):
var gradientColours = coloursBetween("#FF0000", "#0000FF", 5);
which you can use to for something like this:
Here's a script that does just what you're asking for:
https://gist.github.com/av01d/538b3fffc78fdc273894d173a83c563f
Very easy to use:
let colors;
colors = ColorSteps.getColorSteps('#000', 'rgba(255,0,0,0.1)', 10);
colors = ColorSteps.getColorSteps('red', 'blue', 5);
colors = ColorSteps.getColorSteps('hsl(180, 50%, 50%)', 'rgba(200,100,20,0.5)', 10);

Categories

Resources