9
votes

Affichage des boutons ajustés en taille sur des points spécifiques sur une ligne en HTML / CSS

Je suis un débutant en HTML / CSS et je suis en train de créer une application Web avec Django.

Cette application tire ses données d'une base de données précalculée. Chaque entrée a une certaine longueur et contient plusieurs entrées enfants, dont la longueur de chacune est une fraction de la longueur totale de l'entrée parent. En tant que dictionnaire python, cela ressemble à ceci:

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

La longueur de chaque enfant est ici de fin-début.

Ce que j'essaie de faire est d'afficher chaque entrée sous forme de ligne en HTML / CSS, et chaque entrée enfant est affichée sous forme de bouton sur cette ligne. La taille de chaque bouton sur la ligne doit refléter sa longueur (qui est une fraction de la longueur de l'entrée parent, mais elle est différente pour chaque entrée enfant). Il est important de noter que chaque entrée enfant a une certaine position sur l'entrée parent (par exemple: la longueur de l'entrée parent est de 10000, l'enfant 1 est à 1-1000, l'enfant 2 est à 2000 à 6000 et ainsi de suite)

Le résultat auquel je veux arriver est quelque chose comme ceci: entrez la description de l'image ici

J'ai plusieurs dizaines d'entrées que je veux afficher comme ça, éventuellement même en établissant des connexions graphiques d'une entrée à celle affichée ci-dessous. (Disons dessiner une ligne de l'entrée 1, position 1200 à l'entrée 2, position 400).

J'ai réussi à placer les boutons sur une ligne en HTML / CSS, mais je n'ai aucune idée de comment procéder pour ajuster chaque bouton de manière appropriée ou comment les mettre à la bonne position sur la ligne parente.

Quelqu'un pourrait-il m'indiquer du code, des bibliothèques, des méthodes, des tutoriels ou d'autres choses qui pourraient m'aider à atteindre cet objectif?


1 commentaires

S'il s'agit d'une série de boutons enfants dans une rangée, comment souhaitez-vous l'organiser? En ligne droite avec curseur de débordement.


3 Réponses :


10
votes

Cela peut être fait en CSS pur. L'astuce consiste à utiliser des variables CSS et la fonction calc() pour calculer dynamiquement la largeur et la position de départ en% d'un graphe entier. Cela rend le graphique réactif.

Comment ça marche

Un graphique est construit avec un seul élément hr et un nombre variable de nœuds enfants. Les nœuds sont positionnés absolument à l'intérieur de l'élément graphique. La longueur et la position d'un nœud sont calculées à l'aide de la fonction calc() .

La longueur du graphique est définie avec la variable --graph-length .
Les nœuds ont deux variables --start et --end .

La largeur d'un nœud est calculée avec:

<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>

et sa position de départ avec:

.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;
}

Dans le modèle Django, remplacez les valeurs codées en dur par les valeurs du dictionnaire. Si vous pouvez créer une liste d'enfants dans le dictionnaire comme ceci:

<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> 

puis générer un graphique à l'intérieur du modèle Django sera simple comme:

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

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


1 commentaires

Merci, je l'ai fait fonctionner après quelques réajustements pour mes dictionnaires! Il faut en savoir un peu plus sur la mise à l'échelle et tout, mais une réponse bonne et simple!



4
votes

Vous n'avez pas besoin d'une bibliothèque ou de toute autre folie. Il vous suffit de définir quelques styles de base, puis de modifier les propriétés de position et de width via Javascript.

Je le mettrais en place comme ceci:

Vous avez besoin que le HTML corresponde à la mise en page de vos données, alors utilisons ces classes. Ensuite, nous utiliserons CSS pour créer l'alignement et javascript pour tout mettre au bon endroit. Je suggère également de calculer un pourcentage afin que vous puissiez avoir n'importe quelle ligne de largeur (parent).

<div id="parent">
  <button class="child">1</button>
  <button class="child">2</button>
  <button class="child">3</button>
  <button class="child">4</button>
</div>
#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.
  */
}
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  + '%'
});


0 commentaires

1
votes

À première vue, votre problème semble plus compliqué à résoudre qu'il ne l'est en réalité. L'astuce est d'ignorer votre référence à Django, eyecandy comme les lignes et la coloration et de simplifier votre question pour:

Étant donné la largeur (fixe) d'un parent, répartissez uniformément les éléments enfants dans le parent en fonction de leur largeur.

  • Nous obtenons le parent.width de la base de données, le DjangoParentSizeValue
  • Aussi à partir de la base de données, nous connaissons la largeur des éléments à afficher: DjangoHighValue - DjangoLowValue = child.width

Le mécanisme Flexbox fournit déjà un moyen de distribuer les éléments enfants, alors pourquoi ne pas l'utiliser:

Parent : display: flex => utilise par défaut une ligne flexbox sans enveloppe de 1: N colonnes.

Enfant : flex-grow: 'javascript calculated'=> high - low = value . C'est vrai, ignorez la valeur de width CSS, définissez simplement la valeur CSS flex-grow .

Une valeur flex-grow > 0 signifie que l'élément est autorisé à se développer à l'intérieur du parent. Il vaut généralement 0 ou 1 (pas de croissance = par défaut / croissance), mais il peut avoir n'importe quelle valeur. En donnant aux éléments enfants une valeur flex-grow calculée à partir des données de la base de données Django (au lieu de 0/1), le mécanisme Flexbox les répartira uniformément à l'intérieur du parent. Juste ce dont nous avons besoin !!

Et c'est vraiment ça, le reste est eyecandy.

Il ne vous reste plus qu'à décider quoi faire de la valeur de largeur parente extraite de la base de données: 100%, x fois la largeur de la fenêtre, x fois la largeur d'affichage d'un smartphone, etc.

Vous devrez de toute façon définir une limite sur la largeur du parent de toute façon, les utilisateurs détestent devoir faire défiler ou balayer vers la gauche / droite plusieurs fois (je sais que je le fais!).

Le eyecandy: tous sauf le dernier élément enfant ont une margin-right (quelle que soit la valeur souhaitée).

Pour les lignes intermédiaires, j'utilise simplement une image SVG qui trace une ligne sur l'arrière-plan parent.

L'exemple pseudo javascript montre une simple boucle for attribuant flex-grow valeurs flex-grow aux éléments enfants.

Le code semble beaucoup, mais 99% n'est qu'un exemple.

Enfin: vérifiez LeaderLine si vous avez besoin que vos lignes soient cliquables et que vous ayez des fonctionnalités derrière. Je viens de découvrir cette bibliothèque (avec la source Github) et cela semble très amusant ...

<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>
/*
    - 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 }
/*
    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;
        }
    }
};


0 commentaires