Отображение отрегулированных по размеру кнопок в определенных точках на линии в HTML / CSS - PullRequest
9 голосов
/ 24 июня 2019

Я новичок в HTML / CSS и сейчас создаю веб-приложение, используя Django.

Это приложение извлекает данные из предварительно рассчитанной базы данных. Каждая запись имеет определенную длину и содержит несколько дочерних записей, длина каждой из которых является частью всей длины родительской записи. Как словарь Python это выглядит так:

{entry1: {'length': 10000, {child1}: {'start':1, 'end':1000 }, {child2}: {'start':2000, 'end':6000}, ...}

Длина каждого дочернего элемента здесь заканчивается.

Я пытаюсь отобразить каждую запись в виде строки в HTML / CSS, а каждая дочерняя запись отображается в виде кнопки в этой строке. Размер каждой кнопки в строке должен отражать ее длину (которая является частью длины родительской записи, но она отличается для каждой дочерней записи). Важно отметить, что каждая дочерняя запись имеет определенную позицию в родительской записи (например: длина родительской записи составляет 10000, дочерняя 1 - от 1 до 1000, дочерняя 2 - от 2000 до 6000 и т. Д.)

Результат, к которому я хочу добраться, примерно такой: enter image description here

У меня есть несколько десятков записей, которые я хочу отображать таким образом, в конечном итоге даже создавая графические соединения от одной записи к отображаемой ниже. (Давайте нарисуем линию от входа 1, позиции 1200 до позиции 2, позиции 400).

Мне удалось разместить кнопки на строке в HTML / CSS, но я не представляю, как правильно настроить каждую из кнопок или как правильно расположить их в родительской строке.

Может кто-нибудь указать мне на код, библиотеки, методы, учебные пособия или другие вещи, которые могут помочь мне достичь этого?

Ответы [ 3 ]

10 голосов
/ 27 июня 2019

Это можно сделать в чистом CSS.Хитрость заключается в том, чтобы использовать переменные CSS и функцию calc() для динамического вычисления ширины и начального положения в виде% от всего графика.Это делает график отзывчивым.

Как это работает

График строится с одним элементом hr и переменным числом дочерних узлов.Узлы расположены абсолютно внутри элемента графа.Длина и положение узла вычисляется с использованием функции calc().

Длина графика задается с помощью переменной --graph-length.
Узлы имеют две переменные --start и --end.

Ширина узла вычисляется с помощью:

calc((var(--end) - var(--start)) / var(--graph-length) * 100%)

и его начальная позиция с:

calc(var(--start) / var(--graph-length) * 100%)

Внутри шаблона Django замените жестко закодированные значения значениями из словаря.Если вы можете создать список дочерних элементов в словаре следующим образом:

graph = {'entry': {'length': 10000, 'children': [{'child': {'start': 1, 'end': 1000}}, {'child': {'start': 2000, 'end': 6000}}]}}

, тогда создание графика внутри шаблона Django будет простым как:

<div class="graph" style="--graph-length: {{ entry.length }};">
  <hr class="line">
    {% for node in entry.children %}
        <span class="node" style="--start: {{ node.child.start }}; --end: {{ node.child.end }};"></span>
    {% endfor %}
</div> 

.graph {
  position: relative;
}

.node {
  position: absolute;
  top: 0;
  width: calc((var(--end) - var(--start)) / var(--graph-length) * 100%);
  left: calc(var(--start) / var(--graph-length) * 100%);
  height: 1em;
  background-color: cornflowerblue;
  display: block;
  border: 1px solid gray;
  border-radius: 3px;
}

.line {
  position: absolute;
  left: 0;
  right: 0;
  height: 0px;
}
<div class="graph" style="--graph-length: 10000;">
  <hr class="line">
  <span class="node" style="--start: 1; --end: 250;"></span>
  <span class="node" style="--start: 400; --end: 800;"></span>
  <span class="node" style="--start: 1500; --end: 3500;"></span>
  <span class="node" style="--start: 6000; --end: 8000;"></span>
  <span class="node" style="--start: 9500; --end: 10000;"></span>
</div>
<br>
<br>
<div class="graph" style="--graph-length: 10000;">
  <hr class="line">
  <span class="node" style="--start: 1; --end: 150;"></span>
  <span class="node" style="--start: 500; --end: 850;"></span>
  <span class="node" style="--start: 1200; --end: 2500;"></span>
  <span class="node" style="--start: 3000; --end: 3200;"></span>
  <span class="node" style="--start: 5000; --end: 6000;"></span>
  <span class="node" style="--start: 6500; --end: 7500;"></span>
  <span class="node" style="--start: 8500; --end: 9000;"></span>
</div>
<br>
<br>
<div class="graph" style="--graph-length: 1000;">
  <hr class="line">
  <span class="node" style="--start: 1; --end: 100;"></span>
  <span class="node" style="--start: 300; --end: 500;"></span>
  <span class="node" style="--start: 650; --end: 750;"></span>
  <span class="node" style="--start: 900; --end: 950;"></span>
</div>
4 голосов
/ 27 июня 2019

Вам не нужна библиотека или любое другое сумасшествие. Вам просто нужно установить несколько базовых стилей, а затем изменить свойства position & width через Javascript.

Я бы настроил это так:

Вам нужен HTML, чтобы соответствовать вашему макету данных, так что давайте использовать эти классы. Затем мы будем использовать CSS для создания выравнивания и javascript, чтобы поместить все в нужное место. Я бы также предложил рассчитать процент, чтобы вы могли иметь любую ширину линии (родитель).

let data = {
  'one': {
    'left' : 0,
    'width' : 20
  },
  'two': {
    'left' : 25,
    'width' : 5
  },
  'three': {
    'left' : 35,
    'width' : 40
  },
  'four': {
    'left' : 80,
    'width' : 5
  }
}

var parentEl = document.getElementById('parent'); 

Object.entries(data).forEach((item,i) => {
  let props = item[1]
  let el = parentEl.getElementsByTagName("button")[i]
  // These are percents in the data if they're px you need to convert
  // startX/parentWidth * 100 & childW/ParentW * 100

  el.style.left = props.left + '%'
  el.style.width = props.width  + '%'
});
#parent {
  /* give the absolute position an anchor */
  position: relative;
  /* margin to make it easier to see */
  margin: 50px 10px;
  
  width: 100%;
  height: 1px;
  background-color: tan;
}

.child {
  /* position it relative to the line */
  position: absolute;
  padding: 0;
  margin: 0;
  min-width: 1px;
  
  /* bring the top above the line */
  top: -50%;
  transform: translateY(-50%);
  
  /* 
  now all the buttons are stacked at the left 
  we'll change that position with javascirpt.
  */
}
<div id="parent">
  <button class="child">1</button>
  <button class="child">2</button>
  <button class="child">3</button>
  <button class="child">4</button>
</div>
1 голос
/ 04 июля 2019

На первый взгляд, ваша проблема выглядит более сложной, чем на самом деле. Хитрость заключается в том, чтобы игнорировать вашу ссылку на Django, такие как линии и цвета, и упростить ваш вопрос до:

Учитывая фиксированную ширину родителя, равномерно распределите дочерние элементы в родительском элементе с учетом их ширины.

  • Мы получаем parent.width из базы данных, DjangoParentSizeValue
  • Также из базы данных мы знаем ширину отображаемых элементов: DjangoHighValue - DjangoLowValue = child.width

Механизм Flexbox уже предоставляет способ распространения дочерних элементов, так почему бы не использовать это:

Parent : display: flex => по умолчанию - строка flexbox без переноса, состоящая из 1: N столбцов.

Ребенок : flex-grow: 'javascript calculated'=> high - low = value. Правильно, игнорируйте значение CSS width, просто установите значение CSS flex-grow.

A flex-grow value> 0 означает, что элемент может расти внутри родительского элемента. Обычно это 0 или 1 (без увеличения = по умолчанию / увеличения), но оно может иметь любое значение. Предоставляя дочерним элементам значение flex-grow, рассчитанное по данным базы данных Django (вместо 0/1), механизм Flexbox будет равномерно распределять их внутри родительского элемента. Как раз то, что нам нужно !!

И это действительно так, все остальное - eyecandy.

Теперь вам нужно только решить, что делать со значением родительской ширины, полученным из базы данных: 100%, x раз ширина области просмотра, x раз ширина экрана смартфона и т. Д.

Вам все равно придется установить ограничение на ширину родительского элемента, в любом случае, пользователи ненавидят много раз прокручивать или пролистывать влево / вправо (я знаю, что знаю!).

Eyecandy: все, кроме последнего дочернего элемента, получают margin-right (любое значение, которое вам требуется).

Для линий между ними я просто использую изображение SVG, которое рисует линию на родительском фоне.

В примере псевдо-javascript показан простой цикл for, присваивающий значения flex-grow дочерним элементам.

Код кажется много, но 99% это просто пример.

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

/*
    Psuedo/None working example Javascript code
 */
function setFlexGrowValues() {
// Get a list of all FBL parents
var parentList = document.querySelectorAll('.parent');
var childList,x,y;

    // Traverse parent list
    for (x = 0; x < parentList.length; x++) {
        // Get all the 'button' child elements inside the parent
    	childList = parentList[x].querySelectorAll('button');
        // Traverse the parent.button.list
        for (y = 0; y < childList.length; y++) {
            // Calculate and assign the the width found in the Django Database 
        	childList[y].style.flexGrow = DjangoHighValue - DjangoLowValue;
        }
    }
};
/*
    - A Flexbox parent container is a none wrapping row (of 1:N columns) by default.
    - In this case FBL container must have some (min-/max-) width set so the FBL mechanism
      can use that width to properly distribute the FBL child elements with the flex-grow value.
    - Width can be 100% (like here), but essentially any value will do (fixed or relative units).
      The FBL mechanism will use the flex-grow value as a 'ratio' for distributing the child elements. 

    - Combined with the 'flex-grow' attribute of the child elements, the only relevant CSS to make  
      things work is the below line. 

    Any other styling like a line, border, spacing, colors is arbitrary and yours to decide upon. 
*/
.parent { display: flex; width: 100% }

/* using some simple elements spacing => eyecandy */
.parent>button      { margin-right: 1rem }

/* No right margin for the last child in line => eyecandy */
.parent>:last-child { margin-right: 0 }

/*
    No need to use calculation for some line to draw, just use an inline svg to do the job
    and draw a line as a background image from parent (0,50%) upto parent (100%,50%) => eyecandy
*/
.parent { background-image: url('data:image/svg+xml, \
                            <svg xmlns="http://www.w3.org/2000/svg"> \
                                <line x1="0" x2="100%" y1="50%" y2="50%" height="1" stroke="black" /> \
                             </svg>');
}

/* irrelevant, again: eyecandy */
button  { height: 1.5rem; border: 1px solid LightGrey; cursor: pointer }
<div class="parent"><!-- 'style' created by CSS or JS. Here as example only! -->
    <button style="flex-grow: 4000; background-color: PaleTurquoise">1</button>
    <button style="flex-grow: 1000; background-color: Chartreuse"   >2</button>
    <button style="flex-grow:  500; background-color: LemonChiffon" >3</button>
    <button style="flex-grow: 1350; background-color: Cornsilk"     >4</button>
    <button style="flex-grow:  750; background-color: GreenYellow"  >5</button>
    <button style="flex-grow:  550; background-color: DarkSeaGreen" >6</button>
</div>

<div class="parent">
    <button style="flex-grow: 1000; background-color: Chartreuse"   >1</button>
    <button style="flex-grow:  750; background-color: GreenYellow"  >2</button>
    <button style="flex-grow: 1350; background-color: Cornsilk"     >3</button>
    <button style="flex-grow:  500; background-color: LemonChiffon" >4</button>
    <button style="flex-grow:  550; background-color: DarkSeaGreen" >5</button>
    <button style="flex-grow: 4000; background-color: PaleTurquoise">6</button>
</div>

<div class="parent">
    <button style="flex-grow:  550; background-color: DarkSeaGreen" >1</button>
    <button style="flex-grow: 4000; background-color: PaleTurquoise">2</button>
    <button style="flex-grow: 1000; background-color: Chartreuse"   >3</button>
    <button style="flex-grow:  500; background-color: LemonChiffon" >4</button>
    <button style="flex-grow:  750; background-color: GreenYellow"  >5</button>
    <button style="flex-grow: 1350; background-color: Cornsilk"     >6</button>
</div>
...