Временнáя диаграмма в виде линии с ветками-событиями

Я хочу сделать хронологическую диаграмму в виде горизонтальной линии, от которой под углом отходят ветки с событиями (слегка похоже на карту ветки метро в вагонах поездов):

введите сюда описание изображения

Я написал это:

.chrono-line-window {
  width: 100%;
  height: 400px;
  border-radius: 15px;
  box-shadow: 0 0 15px 5px rgba(0, 0, 0, 0.25) inset;
  overflow-x: auto;
  padding: 0 70px 0 70px;
  display: flex;
  align-items: center;
}

.chrono-line {
  flex: 0 0 1000px;
  height: 10px;
  background-color: #333;
  display: flex;
  align-items: center;
}

.sub-line {
  width: 200px;
  height: 5px;
  background: #333;
  margin: 3px;
}

.sub-line:before {
  content: "";
  display: block;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #333;
}

.top-branch .sub-line:before {
  transform: translate(-50%, -40%);
}

.bottom-branch .sub-line:before {
  transform: translate(-45%, -35%);
}

.edge-circle {
  width: 25px;
  height: 25px;
  border-radius: 50%;
  background: #333;
}

.edge-circle:first-child {
  transform: translate(-50%, 0px);
}

.edge-circle:last-child {
  margin-left: auto;
  transform: translate(50%, 0px);
}

.branch-container {
  width: 200px;
}

.branch {
  text-align: center;
  transform-origin: left center;
}

.top-branch {
  transform: translateY(-1.5px) rotate(45deg);
}

.bottom-branch {
  transform: translateY(3px) rotate(-45deg);
}

.branch p {
  font-size: 16px;
  margin-left: 40px;
  margin-right: 20px;
}

.branch p:first-child {
  font-weight: 600;
}

.branch p:last-child {
  font-weight: 300;
}
<div class="chrono-line-window">
  <div class="chrono-line">
    <div class="edge-circle"></div>
    <div class="branch-container top-branch-container">
      <div class="branch top-branch">
        <p>Name of procedure</p>
        <div class="sub-line"></div>
        <p>01.01.2000</p>
      </div>
    </div>
    <div class="branch-container">
      <div class="branch bottom-branch">
        <p>Name of procedure</p>
        <div class="sub-line"></div>
        <p>01.01.2000</p>
      </div>
    </div>
    <div class="branch-container top-branch-container">
      <div class="branch top-branch">
        <p>Name of procedure</p>
        <div class="sub-line"></div>
        <p>01.01.2000</p>
      </div>
    </div>
    <div class="branch-container">
      <div class="branch bottom-branch">
        <p>Name of procedure</p>
        <div class="sub-line"></div>
        <p>01.01.2000</p>
      </div>
    </div>
  </div>
</div>

Но так как у .chrono-line установлено display: flex, то линии (.branch-container) генерируются строго одна за другой по горизонтали, а я хочу вручную устанавливать X-координату.

Конечно, можно высчитывать margin'ы, но в дальнейшем планирую генерировать ветки в JS и хотелось бы устанавливать их позицию по X в соответствии с датой события на ветке одной строкой вроде margin-left: N px, где N=ширина_ветки*(дата_события-дата_начала_линии)/(дата_конец_линии-дата_начала_линии)


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

Автор решения: De.Minov

Вариант на Flexbox.

Из минусов, чтобы вращение и положение линии .chrono-item-inner было взаимосвязанно, пришлось ограничить по высоте элемент .chrono-item-date и плясать уже от него :)

Тут между линиями есть отступ, он фиксированный, за счёт использования gap правила у селектора .chrono-inner.

body {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 100%;
  min-height: 100vh;
  font-family: sans-serif;
  color: #333;
  overflow: hidden auto;
  margin: 0;
}

.chrono {
  display: block;
  width: 100%;
  max-width: 600px;
  height: 350px;
  border-radius: 10px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.5) inset;
  overflow: auto hidden;
}

.chrono-inner {
  display: flex;
  justify-content: flex-start;
  align-items: center;
  width: -webkit-fit-content;
  width: -moz-fit-content;
  width: fit-content;
  height: 100%;
  background-image: 
    radial-gradient(circle, #304e87 67.5%, transparent calc(67.5% + 1px)),
    linear-gradient(#304e87, #304e87);
  background-repeat: no-repeat;
  background-position: 40px center, 60px center;
  background-size: 30px 30px, 100% 4px;
  gap: 40px;
  padding: 0 160px 0 120px;
  box-sizing: border-box;
  margin: 0;
  position: relative;
  list-style: none;
}

.chrono-item {
  display: block;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background-color: #304e87;
  position: relative;
}

.chrono-item-inner {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: center;
  gap: 4px;
  background: linear-gradient(#304e87, #304e87) no-repeat 0 calc(100% - 22px) / 100% 2px;
  white-space: nowrap;
  position: absolute;
  padding-left: 60px;
  text-align: center;
  box-sizing: border-box;
  left: 50%;
  bottom: calc(50% - 22px);
  transform-origin: left calc(100% - 22px);
}

.chrono-item:nth-child(odd) .chrono-item-inner {
  transform: rotate(-45deg);
}

.chrono-item:nth-child(even) .chrono-item-inner {
  transform: rotate(45deg);
}

.chrono-item-title {
  font-weight: 700;
}

.chrono-item-date {
  height: 20px;
}
<div class="chrono">
  <ul class="chrono-inner">
    <li class="chrono-item">
      <div class="chrono-item-inner">
        <div class="chrono-item-title">Название события</div>
        <div class="chrono-item-date">01.01.2000</div>
      </div>
    </li>
    <li class="chrono-item">
      <div class="chrono-item-inner">
        <div class="chrono-item-title">Название события<br>Новая строка</div>
        <div class="chrono-item-date">02.01.2000</div>
      </div>
    </li>
    <li class="chrono-item">
      <div class="chrono-item-inner">
        <div class="chrono-item-title">Название события</div>
        <div class="chrono-item-date">01.01.2001</div>
      </div>
    </li>
  </ul>
</div>


а я хочу вручную устанавливать X-координату

Не особо понятно каким образом вы будете их выставлять, относительно чего?

Например, чтобы визуально показать, что между А и Б точками прошло много времени, предлагаю лучше добавить для Б margin-left какой-то величины.

body {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 100%;
  min-height: 100vh;
  font-family: sans-serif;
  color: #333;
  overflow: hidden auto;
  margin: 0;
}

.chrono {
  display: block;
  width: 100%;
  max-width: 600px;
  height: 350px;
  border-radius: 10px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.5) inset;
  overflow: auto hidden;
}

.chrono-inner {
  display: flex;
  justify-content: flex-start;
  align-items: center;
  width: -webkit-fit-content;
  width: -moz-fit-content;
  width: fit-content;
  height: 100%;
  background-image: 
    radial-gradient(circle, #304e87 67.5%, transparent calc(67.5% + 1px)),
    linear-gradient(#304e87, #304e87);
  background-repeat: no-repeat;
  background-position: 40px center, 60px center;
  background-size: 30px 30px, 100% 4px;
  gap: 40px;
  padding: 0 160px 0 120px;
  box-sizing: border-box;
  margin: 0;
  position: relative;
  list-style: none;
}

.chrono-item {
  display: block;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background-color: #304e87;
  position: relative;
}

.chrono-item-inner {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: center;
  gap: 4px;
  background: linear-gradient(#304e87, #304e87) no-repeat 0 calc(100% - 22px) / 100% 2px;
  white-space: nowrap;
  position: absolute;
  padding-left: 60px;
  text-align: center;
  box-sizing: border-box;
  left: 50%;
  bottom: calc(50% - 22px);
  transform-origin: left calc(100% - 22px);
}

.chrono-item:nth-child(odd) .chrono-item-inner {
  transform: rotate(-45deg);
}

.chrono-item:nth-child(even) .chrono-item-inner {
  transform: rotate(45deg);
}

.chrono-item-title {
  font-weight: 700;
}

.chrono-item-date {
  height: 20px;
}
<div class="chrono">
  <ul class="chrono-inner">
    <li class="chrono-item">
      <div class="chrono-item-inner">
        <div class="chrono-item-title">Название события</div>
        <div class="chrono-item-date">01.01.2000</div>
      </div>
    </li>
    <li class="chrono-item">
      <div class="chrono-item-inner">
        <div class="chrono-item-title">Название события<br>Новая строка</div>
        <div class="chrono-item-date">02.01.2000</div>
      </div>
    </li>
    <li class="chrono-item" style="margin-left: 80px">
      <div class="chrono-item-inner">
        <div class="chrono-item-title">Название события</div>
        <div class="chrono-item-date">01.01.2001</div>
      </div>
    </li>
  </ul>
</div>

→ Ссылка
Автор решения: puffleeck

ассортимента ради, альтернатива на псевдо-верстке с гридами

не сильно симпатично конечно, но украшательств тут как бы и нету.

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

или на первой то что еще лишь в планах(в т.ч. с просроченным дедлайном), а во второй то что уже выполнено.

#r, #t, #x > *{position: absolute; width: 90%;}
#x>p{grid-row: 1;}
#x>div{grid-row: 2;}
#r, #t {z-index: 5;}
#r{top: calc(50vh + 1.3em);}
#t{top: 150vh;}
#x {
  display: grid;
  position: relative;
  grid-template-columns: repeat(31, 1fr);
  grid-template-rows: 1px 1px;
  grid-row-gap: 100vh;
  top: 50vh;
}
#x>* {
  border-top: 1px solid black;
  text-align: center;
  width: 150px;
  transform-origin: 0 50%;}
#x>*:before {
  display: block;
  content: attr(data-name);
  font: 1.5em bold;}
#x>*:hover {background: white;}
#x>*:hover:after{content: attr(data-text);}
#x>*:nth-child(odd) {transform: rotateZ(45deg);}
#x>*:nth-child(even) {transform: rotateZ(-45deg);}
<input id='r' type='range' min='0' max='28'>
<input id='t' type='range' min='0' max='31'>
<div id='x'>
  <p style='grid-column: 2; /* это типа дата такая */'
    data-name='днюха'
    data-text='чья-то'></p>
  <p style='grid-column: 5;'
    data-name='пятое'
    data-text='февраля'></p>
  <p style='grid-column: 14;'
    data-name='<3'
    data-text='ййййййй'>
    aaaaa
    </p>
  <p style='grid-column: 15;'
    data-name='йцукен'
    data-text='ййййййййй!'></p>
  <p style='grid-column: 16;'
    data-name='----'
    data-text='!!1'></p>
    
  <div style='grid-column: 1;'>1 марта</div>
  <div style='grid-column: 9;'>hhhhh</div>
  <div style='grid-column: 15;'>bgbgbgb</div>
  <div style='grid-column: 16;'>vvvvv</div>
</div>

впрочем... почему бы вместо плясок с позиционированием не повернуть весь грид целиком? :3

#x, #y{
  display: inline-grid;
  grid-auto-rows: 1fr;
  border-left: 20px solid #bbb; /* чем это не timeline? */
  margin: 1em;
}
#x>*, #y>*{display: list-item;}
#x>*:after, #y>*:after{content: ' ' attr(style);}
#y{transform: rotateZ(-90deg); transform-origin: 100% 0;}
#y>*{transform: rotateZ(45deg);}
#y>*:nth-child(odd){transform: rotateZ(135deg); transform-origin: 0 0;}
<div id='x'>
  <b style='grid-row: 1'>x</b>
  <b style='grid-row: 2'>x</b>
  <b style='grid-row: 5'>x</b>
  <b style='grid-row: 7'>x</b>
  <b style='grid-row: 15'>x</b>
  <b style='grid-row: 14'>x</b>
  <b style='grid-row: 28'>x</b>
</div>
<div id='y'>
  <b style='grid-row: 1'>y</b>
  <b style='grid-row: 2'>y</b>
  <b style='grid-row: 5'>y</b>
  <b style='grid-row: 7'>y</b>
  <b style='grid-row: 15'>y</b>
  <b style='grid-row: 14'>y</b>
  <b style='grid-row: 28'>y</b>
</div>

→ Ссылка