Color Scale over List - javascript

I have an unordered list that may contain up to 10 items. Within each li is a numeric value. I want the first number to be green, the last number to be yellow, and each number between to be scaled between the two accordingly.
I'm curious if this can be done with css3 alone, or if I'd need JavaScript to calculate the interim and apply it (I could probably work out a JS solution on my own, but I don't know it's worth it at the moment)
Essentially I'd replace the number-good classes (e.g., green) with the scaled value instead.
<ul id="dash-top-customer-list" class="tb-list">
<li class="tb-list-item" data-dym-id="8">
<span class="tb-list-name">OEM2</span><span class="tb-list-value number-good">$208,057</span>
</li>
<li class="tb-list-item" data-dym-id="13">
<span class="tb-list-name">Potential3</span><span class="tb-list-value number-good">$206,988</span>
</li>
<li class="tb-list-item" data-dym-id="9">
<span class="tb-list-name">REP1</span><span class="tb-list-value number-good">$191,029</span>
</li>
<li class="tb-list-item" data-dym-id="14">
<span class="tb-list-name">Potential4</span><span class="tb-list-value number-good">$187,609</span>
</li>
<li class="tb-list-item" data-dym-id="15">
<span class="tb-list-name">Potential5</span><span class="tb-list-value number-good">$183,372</span>
</li>
</ul>
(this is hardly a must-have, and the question is more a curiosity than anything - I was surprised I couldn't find a pertinent question already asked (or my google-fu is really in the can today))

If your list is changing, and you want to recalculate the values, you might want to consider JavaScript. Especially if you want to order the list. This sounds a lot like what d3.js helps with, so it might be worthwhile to have a look into that as it allows you to create color scales.
Another option in JS would be to set up an array of colors and then have a switch statement that assigns colors to the DOM elements.
If it isn't dynamic, then you can simply set up 10 color / classes in your stylesheet and give the DOM elements the class that you want each element to have.

Something like this perhaps?
window.addEventListener('load', onDocLoaded, false);
function onDocLoaded(evt)
{
colorizeListItems( document.getElementById('dash-top-customer-list'), -60, 120 );
}
function colorizeListItems(targetUl, hueRange, hueMin)
{
var liNodeList = targetUl.getElementsByTagName('li');
var liArray = [].slice.call(liNodeList);
var valueArray = liArray.map( function(liElem) { return liElem.querySelectorAll('span')[1].textContent.replace(/,*\$*/g, ''); } );
var largest,smallest,difference;
largest = smallest = valueArray[0];
valueArray.forEach( function(curVal) {if (curVal < smallest) smallest = curVal; if (curVal > largest) largest = curVal;} );
difference = largest - smallest;
liArray.forEach( function(elem, index)
{
var curScaledVal = (valueArray[index] - smallest) / difference;
var curHue = (curScaledVal * hueRange) + hueMin;
var curStr = "hsl(" + curHue.toFixed(0) + ", 100%, 50%);";
elem.setAttribute('style', 'background: ' + curStr);
}
);
}
<ul id="dash-top-customer-list" class="tb-list">
<li class="tb-list-item" data-dym-id="8">
<span class="tb-list-name">OEM2</span><span class="tb-list-value number-good">$8,000,000</span>
</li>
<li class="tb-list-item" data-dym-id="8">
<span class="tb-list-name">OEM2</span><span class="tb-list-value number-good">$5,000,000</span>
</li>
<li class="tb-list-item" data-dym-id="8">
<span class="tb-list-name">OEM2</span><span class="tb-list-value number-good">$6,000,000</span>
</li>
<li class="tb-list-item" data-dym-id="8">
<span class="tb-list-name">OEM2</span><span class="tb-list-value number-good">$7,000,000</span>
</li>
</ul>

Related

Concatenate a list of numbers into a list with .innerHTML

I've got a list of items and I need to create a function that puts a number front of each item in the list. So "pine nuts", would become "2. pine nuts"
Here's the list I'm passing in (with the parameter ulPassed) -
<li id="one" class="hot"><em>fresh</em> figs</li>
<li id="two" class="hot">pine nuts</li>
<li id="three" class="hot">honey</li>
<li id="four" class="cold">balsamic vinegar</li>
<li id="five" class="cold">yoghurt coated banana chips</li>
Here's my code so far -
function swapInnerHTML(ulPassed){
ulPassed = ulPassed.innerHTML;
var grocList = document.getElementById('groceries');
var listItems = grocList.getElementsByTagName('li');
var listNumber = 1;
var count = 0;
for (count = 0; count < ulPassed.length; count++) {
var thisItem = listItems[count].getAttribute('class'); //pointing towards each class item
var foodText = thisItem.firstChild.nodeValue; //pointing towards the child node of each item
foodText = foodText.replace(foodText, listNumber+". "+foodText); //replace the text
thisItem.firstChild.nodeValue = foodText; //update the text
listNumber++; //next list number
}
console.log(ulPassed);
return ulPassed;
}
So I'm attempting a for loop to cycle through the list, and replace each of the food items with itself but with a list number in front of them, also a ". ". In that same loop I'm increasing the list number. The problem I'm having is getting it to replace the text itself, I feel as if I'm pretty close, but I don't feel as if I'm using the firstChild.nodeValue correctly.
Any help would be great. Thanks!
Without using Javascript you could use an ordered list <ol> instead of a unordered list <ul> especially if this is done only for a visual purpose. Also, counters in CSS can be easily styled, e.g.
ul {
counter-reset: food 0;
}
li::before {
counter-increment: food;
content: counter(food) ". ";
}
<ul>
<li class="hot"><em>fresh</em> figs</li>
<li class="hot">pine nuts</li>
<li class="hot">honey</li>
<li class="cold">balsamic vinegar</li>
<li class="cold">yoghurt coated banana chips</li>
</ul>
Otherwise in Javascript you could loop over all the list-items and set their innerHTML property using the index inside the map function
[...document.querySelectorAll('li')].map((food, i) =>
food.innerHTML = [++i, food.innerHTML].join('. ')
);
<ul>
<li class="hot"><em>fresh</em> figs</li>
<li class="hot">pine nuts</li>
<li class="hot">honey</li>
<li class="cold">balsamic vinegar</li>
<li class="cold">yoghurt coated banana chips</li>
</ul>
The suggestion with the use of the map is great, my suggestion is for the more beginners:
const list = document.getElementsByTagName('li');
let arrLi = [].slice.call(list);
for (const [i, v] of arrLi.entries()) {
v.innerHTML = `${i+1} ${v.innerHTML}`
}

For each list, count the total number of elements with matching class

I've spent a lot of time trying to figure this one out and researching for an answer, but none of the answers I've found take into consideration counting each list. I need something that can count the total number of elements with class "link" inside each element with class "nav". I need that number stored as a variable so I can to add a new class to the "nav" elements depending on range qualifiers.
<ul class="nav">
<li class="link"></li>
<li class="link"></li>
<li class="link"></li>
<li class="link">
<ul class="subnav">
<li class="link"></li>
<li class="link"></li>
<li class="link"></li>
<li class="link"></li>
<li class="link"></li>
</ul>
</li>
</ul>
<ul class="nav">
<li class="link"></li>
<li class="link"></li>
<li class="link">
<ul class="subnav">
<li class="link"></li>
<li class="link"></li>
<li class="link"></li>
</ul>
</li>
<li class="link">
<ul class="subnav">
<li class="link"></li>
<li class="link"></li>
</ul>
</li>
</ul>
Any help is greatly appreciated!
You can use this code to count the number of elements with class link inside each element with class nav:
$(document).ready(function(){
$('body').find('.nav').each(function(){
sum = 0;
$(this).find('.link').each(function(){
sum=sum+1;
});
console.log(sum);
});
});
You can do something like this:
var items = $.map( $(".nav"), function(navEl) {
return [navEl, navEl.find(".link").length];
});
console.log( items );
I'm using jQuery.map() to construct a new array based on the .nav elements - each element in the constructed array is array by its own: the first index is the jQuery object, and the second is the count of its children's .link elements.
After some more digging around this morning (and before loading StackOverflow), I was finally able to develop this answer:
$('.nav').each(function(){
var numLinks = $(this).find('.link').length;
if (numLinks > 40) {
$(this).addClass('mm-5-cols');
} else if (numLinks > 30 && numLinks <= 40) {
$(this).addClass('mm-4-cols');
} else if (numLinks > 20 && numLinks <= 30) {
$(this).addClass('mm-3-cols');
} else if (numLinks > 10 && numLinks <= 20) {
$(this).addClass('mm-2-cols');
} else if (numLinks <= 10) {
$(this).addClass('mm-1-col');
}
});
(Apologies if the solution code is slightly off. I modified it from my actual code to match my example code.)
Thanks to those that answered with their own code!

Sorting a list by data-attribute

I have a list of people with job titles sorted by the persons’ first names, like this:
<ul>
<li data-azsort="smithjohn">
<a href="#">
<span class="list-name">John Smith</span>
</a>
<span class="list-desc">Professor</span>
</li>
..
<li data-azsort="barnestom">
<a href="#">
<span class="list-name">Tom Barnes</span>
</a>
<span class="list-desc">Lecturer</span>
</li>
</ul>
I’ve added the data-azsort attribute to the <li> element, and I’d like to pop these list elements into an array, and sort based on that data-* attribute (using plain JavaScript).
What would be the best way to sort the list by data-azsort (A-Z), returning the same code? JavaScript only, no jQuery, etc.
This works for any number of lists: it basically gathers all lis in uls that have your attribute, sorts them according to their data-* attribute value and re-appends them to their parent.
Array.from(document.querySelectorAll("ul > li[data-azsort]"))
.sort(({dataset: {azsort: a}}, {dataset: {azsort: b}}) => a.localeCompare(b)) // To reverse it, use `b.localeCompare(a)`.
.forEach((item) => item.parentNode.appendChild(item));
<ul>
<li data-azsort="skeetjon">
<span class="list-name">Jon Skeet</span>
<span class="list-desc">Stack Overflow user</span>
</li>
<li data-azsort="smithjohn">
<span class="list-name">John Smith</span>
<span class="list-desc">Professor</span>
</li>
<li data-azsort="barnestom">
<span class="list-name">Tom Barnes</span>
<span class="list-desc">Lecturer</span>
</li>
</ul>
<ul>
<li data-azsort="smithjohn">
<span class="list-name">John Smith</span>
<span class="list-desc">Professor</span>
</li>
<li data-azsort="barnestom">
<span class="list-name">Tom Barnes</span>
<span class="list-desc">Lecturer</span>
</li>
<li data-azsort="skeetjon">
<span class="list-name">Jon Skeet</span>
<span class="list-desc">Stack Overflow user</span>
</li>
</ul>
The funny thing is, it gets all lis in the same array, sorts them all, but in the end figures out which list the li originally belonged to. It’s a pretty simple and straight-forward solution.
If you want to sort elements by a numeric data attribute, then use this sort function instead:
// Presumably, the data-* attribute won’t be called `azsort`. Let’s call it `numsort`.
({dataset: {numsort: a}}, {dataset: {numsort: b}}) => Number(a) - Number(b) // `Number(b) - Number(a)` to reverse the sort.
A slightly longer ECMAScript 5.1 alternative would be:
Array.prototype.slice.call(document.querySelectorAll("ul > li[data-azsort]")).sort(function(a, b) {
a = a.getAttribute("data-azsort");
b = b.getAttribute("data-azsort");
return a.localeCompare(b);
}).forEach(function(node) {
node.parentNode.appendChild(node);
});
What about getting all of the list items, push them into array which later will be sorted?
var allListElements = document.getElementById("staff").getElementsByTagName("li");
var staff = new Array();
for (i = 0; i < allListElements.length; i++) {
staff.push(allListElements[i].getAttribute('data-azsort'));
}
staff.sort(function(a, b) {
if (a < b) return -1;
if (a > b) return 1;
return 0;
});
//Print
document.write('<h4>Sorted</h4>');
for (i = 0; i < staff.length; i++) {
document.write(staff[i] + "<br />");
}
<h4>Input</h4>
<ul id="staff">
<li data-azsort="smithjohn">
<a href="#">
<span class="list-name">John Smith</span>
</a>
<span class="list-desc">Professor</span>
</li>
<li data-azsort="barnestom">
<a href="#">
<span class="list-name">Tom Barnes</span>
</a>
<span class="list-desc">Lecturer</span>
</li>
</ul>
Additionally you can save the index of <li> and reorder the <ul>.
You can pass a comparison function to Array.prototype.sort
you should be able to do something like
$items = $('li[data-azsort]');
var compareElem = function (a, b) {
if (a.attr('data-azsort') > b.attr('data-azsort') {
return 1;
} else {
return -1
}
};
Array.prototype.sort.apply($items, compareElem);

get list equal height inside div element

Please take a look at this FIDDLE. I have two pairs of unordered lists, each of which is inside a div element.pricing-table. The following code can find the li with the same classes, get the max height and set the height of all of them to the same. But I want to limit it to getting the max-height of each pair of lists inside each div element.
I think this line is giving me problem because it is getting all the lists with the same classes in the document:
var elems = $('.pricing-table ul li.' + elem.className),
I don't think I can use $(this) and update it like $(this +elem.className). Any suggestions?
Jquery script:
$(document).ready( function(){
$('.pricing-table ul li').each(function(i, elem) {
var elems = $('.pricing-table ul li.' + elem.className),
heights = $.map(elems, function(li) {
return $(li).height();
}),
max = Math.max.apply(null, heights);
elems.height(max);
});
});
HTML
<div class="pricing-table">
<ul>
<li class="heading">Bronze</li>
<li class="year">2003<p>(Text)..........</li>
<li class="package">Starter package</li>
<li class="location">Africa (Text).......)</li>
<li class="description">Text............ </li>
</ul>
<ul class="feature">
<li class="heading">Silver</li>
<li class="year">2004</li>
<li class="package">Intermediate package</li>
<li class="location">Asia</li>
<li class="description">Text............ </li>
</ul>
</div>
<div class="pricing-table">
<ul>
<li class="heading">Bronze</li>
<li class="year">2003<p>(Text)..........</li>
<li class="package">Starter package</li>
<li class="location">Africa (Text).......)</li>
<li class="description">Text............ </li>
</ul>
<ul class="feature">
<li class="heading">Silver</li>
<li class="year">2004</li>
<li class="package">Intermediate package</li>
<li class="location">Asia</li>
<li class="description">Text............ </li>
</ul>
</div>
You’d need to get only the li that are descendants of your current .pricing-table element, so you’ll have to iterate over the latter first:
$(document).ready(function () {
$('.pricing-table').each(function (i, e) {
$(e).find('ul li').each(function (i, elem) {
var elems = $(e).find('ul li.' + elem.className),
heights = $.map(elems, function (li) {
return $(li).height();
}),
max = Math.max.apply(null, heights);
elems.height(max);
});
});
});
… or something like that. http://jsfiddle.net/p3sfy/3867/
(Still kinda ugly, since it will iterate over the li multiple times, so that’s rather just a “quick fix” – but I don’t wanna think about anything more sophisticated here before I have not first heard a convincing argument why this data is not marked up using tables in the first place …?)

jQuery Higlight items that are more than one in ul

I am trying to highlight items that are more than one in a list with different colours with jQuery. Is below achievable easily?
For example. Take the ul below
<ul id="inul">
<li id="s0" class="list">
<span id="ip0">127.0.0.1</span>
<span id="ua0">SonyEricssonK800iv/R1KG Browser/NetFront/3.3 Profile/MIDP-2.0 Configuration/CLDC-1.1</span>
</li>
<li id="s1" class="list">
<span id="ip1">127.0.0.1</span>
<span id="ua1">Nokia3120classic/2.0 (09.41) Profile/MIDP-2.1 Configuration/CLDC-1.1 nokia3120classic/UC Browser7.6.1.82/69/352 UNTRUSTED/1.0</span>
</li>
<li id="s2" class="list">
<span id="ip2">127.0.0.1</span>
<span id="ua2">SonyEricssonW580i/R8BE Browser/NetFront/3.3 Profile/MIDP-2.0 Configuration/CLDC-1.1</span>
</li>
<li id="s3" class="list">
<span id="ip3">127.0.0.1</span>
<span id="ua3">SonyEricssonK800iv/R1KG Browser/NetFront/3.3 Profile/MIDP-2.0 Configuration/CLDC-1.1</span>
</li>
<li id="s4" class="list">
<span id="ip4">127.0.0.1</span>
<span id="ua4">Nokia3120classic/2.0 (09.41) Profile/MIDP-2.1 Configuration/CLDC-1.1 nokia3120classic/UC Browser7.6.1.82/69/352 UNTRUSTED/1.0</span>
</li>
<li id="s5" class="list">
<span id="ip5">127.0.0.2</span>
<span id="ua5">SonyEricssonW580i/R8BE Browser/NetFront/3.3 Profile/MIDP-2.0 Configuration/CLDC-1.1</span>
</li>
</ul>
There are two of each browser user agent and 4 of same ip (127.0.0.1) and 1 127.0.0.2.
What i am hoping to achieve is that identical spans would be colored with same color while assigning different color to each identical set.
Just to be clear, end result should look like image below
UPDATE With the help of WSkid I ahve manged to achive what i want. See update http://pastebin.ca/2058058 or working version at http://jsfiddle.net/mUGVR/15/
The following is terribly in efficient and hacky, but it might start you down the right road of storing a hash-like map and keeping a count to add your needed styles:
Working fiddle http://jsfiddle.net/mUGVR/.
var ipList={};
var ipCount=0;
var userList={};
var userCount=0;
$('li.list').each(function(i){
var spans = $(this).children();
spans[0] = $(spans[0]);
spans[1] = $(spans[1]);
if(ipList[spans[0].text()]!=null)
{
spans[0].addClass('ip'+ipList[spans[0].text()]);
}
else
{
ipList[spans[0].text()] = ipCount;
spans[0].addClass('ip'+ipCount);
ipCount++;
}
if(userList[spans[1].text()]!=null)
{
spans[1].addClass('user'+userList[spans[1].text()]);
}
else
{
userList[spans[1].text()] = userCount;
spans[1].addClass('user'+userCount);
userCount++;
}
});
With css:
.ip0 {background:yellow;}
.user0{background:cyan;}
.user1{background:green;}
.user2{background:red;}
Like this? Fiddle: http://jsfiddle.net/8A5dY/1
// highlights ips
$('li span:first-child').filter(function() {
return ($(this).text() == '127.0.0.1');
}).css('background', 'yellow');
var colorMap = [];
var currentColor = 0;
$('li span:nth-child(2)').each(function() {
var text = $(this).text();
var color = colorMap[text] ||
['lightblue', 'red', 'blue', 'green'][currentColor++];
colorMap[text] = color;
$(this).css('background', color);
});

Categories