CJH
CJH

Reputation: 1594

CSS Conditional Formatting with a colour range

I am looking to create a basic chart using divs and spans and want to apply conditional formatting to each column in the chart depending on the value setting its height. The trick which I haven't been able to crack is that I want to have it function a bit like Excel conditional formatting in the example here:

enter image description here

Where the colours are in a range (light to dark). Is there a simple way of doing this? I can see how I could apply static values to static colours but was hoping I could do something with colour ranges like this excel example.

So, the below screenshot shows a column chart where each column has a different shade of orange determined by the value of the column:

enter image description here

The closer to 25 the column is, the darker the colour.. Like-wise, the lower the value, the lighter the shade of orange is.

Upvotes: 1

Views: 2241

Answers (5)

Harry Mustoe-Playfair
Harry Mustoe-Playfair

Reputation: 1429

I know this is an older question, but this is now possible using CSS variables and color-mix:

let bars = document.querySelectorAll('.bar');

let i = 0;
for (const bar of bars) {
  bar.style.setProperty('--value', `${i+=2}`);
}
.container {
  --min: 0;
  --max: 10;
  --incs: oklch;
  --value: 0.5;
  --high-color: #843c0c;
  --low-color: #fbe5d6;
  display: inline-flex;
  align-items: flex-end;
  background-color: #fff;
  padding: 1rem;
  gap: 3rem;
  border: 1px solid grey;
}

.bar {
  --normed: calc(100% * (var(--value) - var(--min))/(var(--max) - var(--min)));
  background-color: color-mix(
    in var(--incs),
    var(--high-color) var(--normed),
    var(--low-color)
  );
  width: 1.5rem;
  height: calc(1rem * var(--value));
}
<div class="container">
  <div class="bar"></div>
  <div class="bar"></div>
  <div class="bar"></div>
  <div class="bar"></div>
  <div class="bar"></div>
</div>

You can even use 3 colour gradient, like this question here:

Is there a way to interpolate the colour of an element using css, based on a value?

Upvotes: 1

Rickard Elim&#228;&#228;
Rickard Elim&#228;&#228;

Reputation: 7591

Using a combination of my and Justin's answers, but for vanilla CSS.

This answer is using HSL and CSS variables to set the color and the height.

Randomized values

let bars = document.querySelectorAll('.bar');

function randomize(max, min) {
  min = min || 0;
  
  return Math.floor(Math.random() * (max - min) + min);
}

for (const bar of bars) {
  let maxValue = 25;
  let randomValue = randomize(maxValue, 2);
  let height = randomValue/2;
  
  let hue = '24deg';
  let saturation = '82%';
  let maxHue = 90;
  let minHue = 30;
  let hueRange = maxHue - minHue;
  let lightness = `${maxHue - hueRange * (randomValue/maxValue)}%`;
  
  bar.style.setProperty('--bar-height', `${height}rem`);
  bar.style.setProperty('--color-background-bar', `hsl(${hue}, ${saturation}, ${lightness})`);
}
.container {
  display: inline-flex;
  margin-left: 4.5rem;
  align-items: flex-end;
  padding: 0px 1rem;
  
  background: repeating-linear-gradient(to top, #eee, #eee 1px, transparent 1px, transparent 1rem);
}

.bar {
  width: 1.5rem;
  height: var(--bar-height);
  background-color: var(--color-background-bar);
}

.bar + .bar {
  margin-left: 3rem;
}
<div class="container">
  <div class="bar"></div>
  <div class="bar"></div>
  <div class="bar"></div>
  <div class="bar"></div>
  <div class="bar"></div>
</div>

Increasing values

Static values ranging from 5 to 25, as seen in the OP's pic.

let bars = document.querySelectorAll('.bar');

bars.forEach((bar, index) => {
  let maxValue = 25;
  let customValue = 5 + index * 5;
  let height = customValue/2;
  
  let hue = '24deg';
  let saturation = '82%';
  let maxHue = 90;
  let minHue = 30;
  let hueRange = maxHue - minHue;
  let lightness = `${maxHue - hueRange * (customValue/maxValue)}%`;

  bar.style.setProperty('--bar-height', `${height}rem`);
  bar.style.setProperty('--color-background-bar', `hsl(${hue}, ${saturation}, ${lightness})`);
});
.container {
  display: inline-flex;
  margin-left: 4.5rem;
  align-items: flex-end;
  padding: 0px 1rem;
  
  background: repeating-linear-gradient(to top, #eee, #eee 1px, transparent 1px, transparent 1rem);
}

.bar {
  width: 1.5rem;
  height: var(--bar-height);
  background-color: var(--color-background-bar);
}

.bar + .bar {
  margin-left: 3rem;
}
<div class="container">
  <div class="bar"></div>
  <div class="bar"></div>
  <div class="bar"></div>
  <div class="bar"></div>
  <div class="bar"></div>
</div>

Upvotes: 0

Rickard Elim&#228;&#228;
Rickard Elim&#228;&#228;

Reputation: 7591

[edit] This answer only colors the bars from left to right, having the lightest color to the left.

I would let the container have a white background and the bars to be black, and then add a gradient over everything, with help of a pseudo-element that have set mixed-blend-mode: lighten to only colorize the black bars.

As a bonus, I added another pseudo-element with a repeating linear gradient consisting of a really light grey to create the horizontal lines. I then added mixed-blend-mode: darken to this element to make them appear "under" the bars.

I also randomized the height of the bars, by randomizing a CSS property for each bar.

This solution scales, so it doesn't matter how many bars you got, you still get a gradient over all the of bars without having to change the CSS code.

let bars = document.querySelectorAll('.bar');

function randomize(max, min) {
  min = min || 0;
  
  return Math.floor(Math.random() * (max - min) + min);
}

for (const bar of bars) {
  bar.style.setProperty('--bar-height', `${randomize(10, 2)}rem`);
}
.container {
  position: relative;
  display: inline-flex;
  margin-left: 4.5rem;
  align-items: flex-end;
  background-color: #fff;
  padding: 0px 1rem;
}

.container::before,
.container::after {
  content: '';
  position: absolute;
  top: 0px;
  bottom: 0px;
  left: 0px;
  right: 0px;
}

.container::before {
  background: linear-gradient(to right, #fbe5d6, #843c0c);
  mix-blend-mode: lighten;
}

.container::after {
  background: repeating-linear-gradient(to top, #f4f1f1, #f4f1f1 1px, transparent 1px, transparent 1rem);
  z-index: 10;
  mix-blend-mode: darken;
}

.bar {
  width: 1.5rem;
  height: var(--bar-height);
  background-color: #000;
}

.bar + .bar {
  margin-left: 3rem;
}
<div class="container">
  <div class="bar"></div>
  <div class="bar"></div>
  <div class="bar"></div>
  <div class="bar"></div>
  <div class="bar"></div>
</div>

Upvotes: 0

Justin
Justin

Reputation: 2958

I am going to provide two options and maybe you can provide some more details based on these on exactly what you need.

This first one may not be what you want as it sets a specific gradient based on a specific height. Only going to provide a Codepen for this one. https://codepen.io/jfirestorm44/pen/yLMNPPM?editors=1100

This next one is more of what I think you want. If you know the max height of the bar graph you can use that to set the gradient breaks on your linear-gradient.

UPDATED: HTML

<div id="container">
  <div id="first" class="bar"></div>
  <div id="second" class="bar"></div>
  <div id="third" class="bar"></div>
  <div id="fourth" class="bar"></div>
  <div id="fifth" class="bar"></div>
</div>

SCSS

.bar {
  @for $i from 1 through 5 {
    $height: 20px * $i;
    $light: 75% + $i * -5;
    &:nth-child(#{$i}) {
      position: absolute;
      bottom: 50%;
      left: 20% + ($i * 10%);
      width: 20px;
      height: $height;
      font-size: 25px;
      transform: translate(-80%, 0);
      background: hsl(35, 100%, $light);
    }
  }
}

Updated Codepen: https://codepen.io/jfirestorm44/pen/jOBPopj?editors=1100

ADDING a JS Option:

let inputNum = document.getElementById("number");
let button1 = document.getElementById("button1");
let border = document.getElementById("border");
let dropDown = document.getElementById("cars");
function color() {
  if (inputNum.value > 0) {
    let bar = document.createElement("div");
    bar.classList.add("bar");
    border.appendChild(bar);
    let bars = document.getElementsByClassName("bar");

    let carName = document.createElement("p");
    carName.classList.add("carType");
    carName.textContent = cars.options[cars.selectedIndex].text;
    border.appendChild(carName);
    let names = document.getElementsByClassName("carType");

    let height = inputNum.value * 26;
    for (let i = 0; i < bars.length; i++) {
      names[names.length - 1].style.top = "275px";
      names[names.length - 1].style.left = -5 + i * 30 + "px";
      bars[bars.length - 1].style.height = height + "px";
      bars[bars.length - 1].style.backgroundColor =
        "hsl(35, 100%," + (75 - height / 5.2) + "%)";
      bars[bars.length - 1].style.left = 10 + i * 30 + "px";
    }
  }
}
#container {
  position: absolute;
  top: 40px;
  left: 0;
  width: 400px;
  height: 300px;
}
#border {
  position: relative;
  left: 100%;
  top: 0;
  width: 90%;
  height: 90%;
  border-left: 2px solid grey;
  border-bottom: 2px solid grey;
  transform: translate(-100%, 0);
}
#numberContainer {
  position: relative;
  left: -5%;
}
.num {
  line-height: 10px;
}
.num:before {
  content: "";
  position: absolute;
  left: 18px;
  width: 100%;
  height: 10px;
  border-bottom: 1px lightgrey solid;
}
.bar {
  position: absolute;
  bottom: 0;
  width: 20px;
}
#button1 {
  position: relative;
  top: 0;
  width: 60px;
  height: 20px;
}
.car {
  position: absolute;
  top: 10px;
}
.carType {
  position: absolute;
  bottom: -85px;
  writing-mode: vertical-rl;
  text-orientation: upright;
}
<div id="container">
  <div id="border">
    <div id="numberContainer">
      <p class="num">10</p>
      <p class="num">9</p>
      <p class="num">8</p>
      <p class="num">7</p>
      <p class="num">6</p>
      <p class="num">5</p>
      <p class="num">4</p>
      <p class="num">3</p>
      <p class="num">2</p>
      <p class="num">1</p>
      <p class="num">0</p>
    </div>

  </div>
</div>
<input type="number" min="0" max="10" value="0" id="number"/>
<label for="cars">Choose a car:</label>

<select name="cars" id="cars">
  <option value="volvo">Volvo</option>
  <option value="saab">Saab</option>
  <option value="mercedes">Mercedes</option>
  <option value="audi">Audi</option>
</select>
<button type="button" onclick="color()" id="button1">Submit</button>

Upvotes: 2

Auroratide
Auroratide

Reputation: 2577

It sounds like your goal is to color a bar somewhere between two colors depending on a value. If that's the case, you can use css animations to simulate the color gradient.

The idea is this:

  • Set up an animation setting the background to be one of two colors. This effectively calculates a gradient between the two colors. You do this with @keyframes and animation.
  • Pause the animation, since we don't want it to actually play. This is done with animation-play-state.
  • Select a specific frame in the animation to get the correct in-between color. This can be done with a negative animation-delay.

.bars {
  display: flex;
  justify-content: space-evenly;
}

.bar {
  animation: color-gradient 25s linear 1;
  animation-fill-mode: forwards;
  animation-play-state: paused;
  width: 3em;
}

@keyframes color-gradient {
  from { background: red; }
  to   { background: blue; }
}
<div class="bars">
  <div class="bar" style="height: 5em; animation-delay: -5s;"></div>
  <div class="bar" style="height: 10em; animation-delay: -10s;"></div>
  <div class="bar" style="height: 15em; animation-delay: -15s;"></div>
  <div class="bar" style="height: 20em; animation-delay: -20s;"></div>
  <div class="bar" style="height: 25em; animation-delay: -25s;"></div>
</div>

The granularity can be adjusted by making the animation duration longer than 25 seconds if need be.

Upvotes: 3

Related Questions