Объемный график на CSS или JS

График

Подскажите как сделать такой график, в интернете не могу найти что-то похожее на это
Есть много библиотек для построения графиков на JS, но я не знаю какая из них может это
И можно ли это сделать на чистом CSS?


Ответы (1 шт):

Автор решения: UModeL

И можно ли это сделать на чистом CSS?

Ну, совсем без скриптов нельзя. Но, большая часть вполне реализуема посредством стилей (смотреть лучше развернув на весь экран):

let oJson = JSON.parse(`{
  "Понедельник": [47,230,1,164,20,219,72,235,145,209,25,205],
  "Вторник": [175,38,13,151,185,237,85,126,103,125,153,165],
  "Среда": [112,19,183,201,21,64,60,35,193,236,73,207],
  "Четверг": [50,192,211,148,198,86,217,32,49,113,218,62],
  "Пятница": [69,54,210,74,7,222,78,92,77,142,162,23],
  "Суббота": [101,39,212,157,131,139,178,149,206,147,71,122],
  "Воскресенье": [46,11,109,26,43,158,28,227,34,90,121,129]
}`);

function fFillChart() {
  let oChart = document.querySelector(".girder-chart");
  let nItems = 0;
  let nQ = Math.max(...Object.values(oJson).flat()) / 100;
  for (let k in oJson) {
    let oRow = document.createElement("div");
    oChart.append(oRow);
    oJson[k].forEach((el) => {
      let nValue = el / nQ;
      nItems++;
      oRow.insertAdjacentHTML("beforeend",
        `
        <div
          class="girder" 
          data-grp="${Math.floor(nValue / 25)}" 
          style="
          transition-delay:${nItems / 100}s;
          --girder-height: ${nValue};
          filter: 
          drop-shadow(0 2px 3px #0008) 
          hue-rotate(${(100 - nValue) * 3 - 45}deg) 
          saturate(${(nValue / 100) * 0.75});
          opacity:${nValue / 110 + 0.1};
          ">
            <div class="girder_l"></div>
            <div class="girder_r"></div>
            <div class="girder_t" data-val="${el}"></div>
        </div>
        `
      );
    });
  }
}

fFillChart();
body {
  margin: 0;
  min-height: 100vh;
  background-image: radial-gradient(#181100, #000);
  background-position: 0% 110%;
  background-repeat: no-repeat;
  background-size: auto;
  display: flex;
  flex-flow: column nowrap;
  justify-content: space-around;
  align-items: center;
}

.girder-wrap {
  display: flex;
  flex-flow: row wrap;
  justify-content: center;
  align-items: center;
  width: min-content;
  color: #fff;
  text-shadow: -1px -1px 3px #000, -1px 1px 3px #000, 1px -1px 3px #000, 1px 1px 3px #000;
}

.girder-wrap>input:not(:first-child) {
  margin-left: 2em;
}

hr {
  margin: 20px 40px 80px;
  width: 100%;
}

.girder-chart {
  --girder-think: 50;
  font-size: calc(var(--girder-think, 100) * 1px) !important;
  position: relative;
  display: inline-flex;
  flex-flow: column nowrap;
  justify-content: flex-start;
  align-items: flex-start;
  padding: 1em 2em;
}
.girder-chart::before {
  content: "";
  position: absolute;
  top: 0em; left: 0.23em;
  height: 100%; width: calc(100% - 0.49em);
  background-image: repeating-linear-gradient( 210.9deg, #0000 0 calc(0.49em - 1px), #8888 0.49em, #0000 calc(0.49em + 1px) 0.514em), repeating-linear-gradient( -210.9deg, #0000 0 calc(0.24em - 1px), #8888 0.24em, #0000 calc(0.24em + 1px) 0.514em);
  -webkit-mask-image: radial-gradient(ellipse closest-side, #0000, #000, #0000);
  mask-image: radial-gradient(ellipse closest-side, #0000, #000, #0000);
  pointer-events: none;
}

.girder-chart>div {
  display: flex;
  flex-flow: row nowrap;
  justify-content: flex-start;
  align-items: flex-end;
  height: 0.3em;
}
.girder-chart>div:nth-child(odd) { margin-right: calc(var(--girder-think) / 2 * 1px); }
.girder-chart>div:nth-child(even) { margin-left: calc(var(--girder-think) / 2 * 1px); }

.girder {
  transform: translate3d(0, 0, 0);
  position: relative;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  will-change: height, opacity;
  height: calc(var(--girder-height, 0) * 1px + 0.3em) !important; width: 1em;
  filter: drop-shadow(0 3px 1px #000a) saturate(100%);
  transition: height 0.4s cubic-bezier(0.65, 0.45, 0.33, 1.18), opacity 0.5s ease-in-out 0s;
}

.girder_t {
  position: relative;
  height: 0.68em; width: 0.68em;
  border-radius: 0.066em 0.066em 0.066em 0.033em;
  transform-origin: 50% 50%;
  transform: rotate(-45deg) skew(15deg, 15deg) translate(48%, -48%) scale(0.87);
  background-image: linear-gradient(135deg, #fa0f, #fa0d);
  box-shadow: inset -0.1em -0.155em 0.45em -0.01em #fa0f, -0.02em 0.02em 0.033em -0.01em #fa0a;
}
/* Плашка со значением */
.girder_t::before,
.girder_t::after {
  position: absolute;
  bottom: 50%; left: 100%; z-index: 1;
  font-size: initial !important;
  opacity: 0;
  transition: transform 0.4s cubic-bezier(0.65, 0.45, 0.33, 1.18), opacity 0.4s linear 0s;
  pointer-events: none;
}
.girder_t::before {
  content: "";
  height: 2em;
  transform: rotate(45deg) skew(0deg, 0deg) translate(-50%, 0%) scale(1, 1.5);
  box-shadow: 0 0 0 1px #f0f;
}
.girder_t::after {
  content: attr(data-val);
  padding: 0.5em 0.7em;
  border-radius: 0.3em;
  transform: rotate(45deg) skew(0deg, 0deg) translate(-33%, -1em) scale(0.1, 1.5);
  font-family: sans-serif;
  text-align: center;
  background-color: #eee;
  box-shadow: inset 0 0 0 2px #f0f;
}
.girder:hover .girder_t::before,
.girder:hover .girder_t::after {
  opacity: 1;
  transition: transform 0.4s cubic-bezier(0.65, 0.45, 0.33, 1.18) 1s, opacity 0.4s linear 1s;
}
.girder:hover .girder_t::after {
  transform: rotate(45deg) skew(0deg, 0deg) translate(-33%, -1em) scale(1, 1.5);
}

.girder_l,
.girder_r {
  position: absolute;
  height: calc(100% - 0.3em); width: calc(50% + 0.015em);
}
.girder_l {
  left: 0;
  border-radius: 0 0 0.055em 0.033em;
  transform-origin: 0% 0%; transform: skewy(30deg);
  background-image: linear-gradient(90deg, #fa0f, #805500, #0001);
  box-shadow: inset -0.34em -0.1em 0.25em -0.2em #000e, inset 0.84em 0.54em 0.95em -0.4em #fa08;
}
.girder_r {
  right: 0;
  border-radius: 0 0 0.033em 0.055em;
  transform-origin: 100% 0%; transform: skewy(-30deg);
  background-image: linear-gradient(90deg, #000c, #805500);
  box-shadow: inset -0.125em 0.1em 0.425em -0.225em #fa0c;
}

input[name="grp"][value=""]:checked~.girder-chart:hover>div>div:not(:hover) {
  height: 0.3em !important;
  transition: height 1s ease-in-out, opacity 0.5s ease-in-out;
  transition-delay: 0s !important;
}
input[name="grp"][value="0"]:checked~.girder-chart>div>div:not([data-grp="0"]),
input[name="grp"][value="1"]:checked~.girder-chart>div>div:not([data-grp="1"]),
input[name="grp"][value="2"]:checked~.girder-chart>div>div:not([data-grp="2"]),
input[name="grp"][value="3"]:checked~.girder-chart>div>div:not([data-grp="3"]) {
  height: 0.3em !important;
  opacity: 0 !important;
  pointer-events: none;
  transition: height 1s ease-in-out, opacity 0.5s ease-in-out;
  transition-delay: 0s !important;
}

.girder-chart:hover>div>div:hover {
  opacity: 1 !important;
  transition-delay: 0s !important;
}
<div class="girder-wrap">
  <input type="radio" name="grp" value="" checked>Все
  <input type="radio" name="grp" value="0">0-25%
  <input type="radio" name="grp" value="1">26-50%
  <input type="radio" name="grp" value="2">51-75%
  <input type="radio" name="grp" value="3">76-100%
  <hr>
  <div class="girder-chart"></div>
</div>

Функция лишь раскидывает входящие данные по создаваемым элементам, одновременно управляя внешним видом столбцов. Ненужные эффекты (разный цвет, прозрачность) можно закомментировать или удалить.

Как правильно заметили в комментариях под вопросом - загвоздка в формате входящих данных и их парсинге (просто от этого нужно изначально отталкиваться).

→ Ссылка