Génération d'un graphe : mise à l'échelle des barres en largeur

Le problème exposé dans ce sujet a été résolu.

Edit :

Le problème a été résolu. La solution se trouve dans la partie "Résolution" en fin de message (+ d'infos en lisant la première page de ce topic).

Bonjour tout le monde,

Contexte

Je dois créer un module de statistiques qui affiche un graphe dont les données sont stockables dans ses axes, par un système de drag'n drop. Les données sont affichées sous forme de barres verticales.

La génération du graphe se fait grâce à D3.js, une bibliothèque plus compliquée que d'autres qui permet cependant de faire plus de choses, plus en détails. L'usage ou le non-usage de cette bibliothèque n'a pas de conséquence sur mon problème.

Lorsque je passe mes données chiffrées en entrée du générateur de graphe, celui-ci procède à une mise à l'échelle de la hauteur, pour que chaque barre ait sa propre hauteur tout en s'assurant que la hauteur de la plus haute barre, égale à son ordonnée mise à l'échelle, ne dépasse pas la hauteur du graphe.

Je n'ai pas vraiment eu de difficulté pour mettre à l'échelle la hauteur. Vous trouverez en annexe le code qui s'en occupe. Par contre, je bute sur la mise à l'échelle de la largeur.

Problème

Je n'arrive pas à trouver la formule qui mette à l'échelle la largeur des barres :

  1. Chaque barre doit avoir sa propre largeur, égale à son abscisse mise à l'échelle (fait !);
  2. La largeur cumulée de chaque barre ne doit pas dépasser celle du graphe (échec !).

Le gros soucis que je rencontre, c'est donc que les barres dépassent horizontalement de mon graphe.

Ce que je souhaite obtenir

Voici le genre de graphe que je souhaite créer (considérez la largeur de chacune de ses barres, c'est ce que je veux mettre en place) : Graphe à barres variables en largeur + mise à l'échelle de la largeur de chaque barre.

Ce que j'ai obtenu

Note : sur chaque barre est inscrit "(<a>;<b>)". Bien entendu, <a> est l'abscisse et <b>, l'ordonnée.

Avec la version A du code JS (cf. la partie suivante) Les barres dépassent du graphe + notez la deuxième barre qui commence, à droite (elle commence par le haut, c'est étonnant... mais sûrement dû au problème de largeur). Les barres dépassent du graphe + notez la deuxième barre qui commence, à droite (elle commence par le haut, c'est étonnant… mais sûrement dû au problème de largeur).

Avec la version A-variante-1

C'est pour l'instant la version qui correspond le mieux à ce que je souhaite obtenir. Les largeurs me semblent tout à fait cohérentes ! Malheureusement, elles sont trop petites, et les barres ne remplissent pas le graphe... Les largeurs me semblent tout à fait cohérentes ! Malheureusement, elles sont trop petites, et les barres ne remplissent pas le graphe…

Avec la version A-variante-2

http://nsa37.casimages.com/img/2015/05/19/150519042002871529.png

Avec la version B Les largeurs ne sont pas cohérentes. Les largeurs ne sont pas cohérentes.

Avec la version C http://nsa38.casimages.com/img/2015/05/19/150519025013593554.png

Ce que j'ai codé jusqu'à maintenant pour essayer de résoudre mon problème*

Parent-direct du parent-direct des barres du graphe (note : leur parent-direct ne possède pas de code CSS):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#graph
{
    position : absolute;
    width : 100%; /* Important, non ? */
    background : url("img/grille.png"), linear-gradient(#373737, #000);
    right : 0;
    left : 55px;
    top : 125px;
    bottom : 0;
    border-radius : 0 0 10px 0; 
}

Code JS pour déterminer la largeur mise à l'échelle de chaque barre :

Indication : plusieurs versions sont à l'essai. Pour l'instant, aucune ne solutionne le problème.

Version A

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var width = $(this.css_selector).width(); // Récupération de la largeur du graphe
var max_value_x = 0;
for (var i = 0; i < this.array_JSON.length; i++) { // this.array_JSON = [{x, y}] où x : abscisse et y : ordonnée
        if (max_value_x < this.array_JSON[i].x) { // Cette boucle et cette condition servent juste à savoir quelle est la plus grande abscisse
                max_value_x = this.array_JSON[i].x;
        }
}

// Indication de la largeur de la barre qui va être créée :
new_Node.style("width", function (data) { return data.x/max_value_x*width + "%"; }); // data : un élément de this.array_JSON

Version A-variante-1

Modification à effectuer : return data.x/max_value_x*width + "%"; DEVIENT return data.x/max_value_x + "%";

Version A-variante-2

Modification à effectuer : return data.x/max_value_xwidth + "%"; DEVIENT return data.x/max_value_x(total_x/width) + "%";

Version B

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
var max_value_x = 0;
var total_x = 0;
var last_x = 0;

for (var i = 0; i < this.array_JSON.length; i++) {
    if (max_value_x < this.array_JSON[i].x) {
        max_value_x = this.array_JSON[i].x;
    }
    total_x += this.array_JSON[i].x;
    this.array_JSON[i].width = this.array_JSON[i].x - last_x;
    last_x = this.array_JSON[i].x;
}

new_Node.style("width", function (data) { return data.width/total_x*100 + "%"; });

Version C :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var width = $(this.css_selector).width();   
var total_x = 0;

for (var i = 0; i < this.array_JSON.length; i++) {      
    total_x += this.array_JSON[i].x;
}

var width_divided = width/total_x;

new_Node.style("width", function (data) { return data.x*width_divided + "%"; });

Annexes

Code JS complet du générateur (vous y trouverez la mise à l'échelle de la hauteur de chaque barre comme indiqué en début de ce message, ainsi que celle de la largeur, évidemment) :

Indication : ce code utilise la version A du JS (cf. partie précédente). Il est très facile de localiser les lignes à changer pour utiliser les autres versions !

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Graph.prototype.generateGraph = function()
{
    $(this.css_selector).empty();
    $(this.css_selector).append("<section>");
    var width = $(this.css_selector).width();
    var height = $(this.css_selector).height();

    var array_selected_nodes = d3.select(this.css_selector+">section:first-child").selectAll(this.d3_js_select_option);
    var update_Selection = array_selected_nodes.data(this.array_JSON);

    var min_value_y  = 0;
    var max_value_y = 0;
    var max_value_x = 0;

    for (var i = 0; i < this.array_JSON.length; i++) {
        if (min_value_y > this.array_JSON[i].y) {
            min_value_y = this.array_JSON[i].y;
        }
        if (max_value_y < this.array_JSON[i].y) {
            max_value_y = this.array_JSON[i].y;
        }
        if (max_value_x < this.array_JSON[i].x) {
            max_value_x = this.array_JSON[i].x;
        }
    }

    var range_y = max_value_y - min_value_y;
    var height_coef = height / range_y;

    var enter_Selection = update_Selection.enter();
    var new_Node = enter_Selection.append(this.d3_js_select_option);

    new_Node.style("background-color", function (data) { return "rgba(255,144,0, 0.5)"; });
    new_Node.style("display", "inline-block");
    new_Node.style("vertical-align", "bottom");
    new_Node.style("color", "#FFF");

    new_Node.style("width", function (data) { return data.x/max_value_x*width + "%"; });
    new_Node.style("height", function (data) { return data.y*height_coef + "px"; });
    new_Node.text(function(data) { return "(" + data.x+";"+data.y + ")"; });

    new_Node.style("margin-right", "1px");
};

Conclusion

La formule que j'ai placée pour les largeurs donne des valeurs trop importantes, ce qui en conséquence retourne des pourcentages de largeur beaucoup trop grands. Du coup les données dépassent du graphe horizontalement.

Qu'est-ce qui ne va pas dans mon p'tit programme ?

Merci d'avance, et bonne journée !

Résolution

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Graph.prototype.generateGraph = function()
{
    var total_x = 0;

    for (var i = 0; i < this.array_JSON.length; i++) {      
        total_x += this.array_JSON[i].x;
    }

    new_Node.style("width", function (data) { return (data.x/total_x)*100 + "%"; });    
};

Résultat visuel : Largeurs cohérentes + les barres remplissent bien toute la largeur du graphe

Largeurs cohérentes + les barres remplissent bien toute la largeur du graphe

* : partie sujette à des mises-à-jour + cette partie ne contient pas d'annexes (une partie leur est dédiée).

+0 -0

Le principe de base me parait correct :

  • Tu as calculé le total des valeurs
  • Tu as chaque valeur individuellement
  • Tu peux donc calculer le pourcentage de chaque barre
  • Tu n'as plus qu'à calculer la largeur correctement (plutôt en pourcentage si tu veux que ça s'adapte mieux, sinon en pixels)

En gros je pense que tu peux bêtement supprimer la multiplication par width et ça devrait être bon.

Non viki, je n'avais pas calculé le total des valeurs, mais juste la valeur maximale :) . J'ai supprimé la multiplication "width*", les valeurs semblent cohérentes maintenant mais les barres sont trop petites (en largeur). J'aimerais qu'elles prennent autant de place que possible dans le graphe.

Au final je viens de changer mon code. J'ai calculé cette somme dont tu me parles, ainsi que la largeur de chaque barre. J'ai supprimé aussi la variable max_value_x (celle qui t'a visiblement parue être la somme des x alors qu'elle n'était que le plus grand x).

Malheureusement, ça ne marche toujours pas (les barres sont étonnamment petites).

Voici le nouveau code (j'ai màj l'OP) :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    var width = $(this.css_selector).width();
    var total_x = 0;
    var last_x = 0;

    for (var i = 0; i < this.array_JSON.length; i++) {
        total_x += this.array_JSON[i].x;
        this.array_JSON[i].width = this.array_JSON[i].x - last_x;
        last_x = this.array_JSON[i].x;
    }

    new_Node.style("width", function (data) { return data.width/total_x + "%"; });

Edit : mais en fait, si on revient à la première version de mon code (celle que tu viens de commenter), il suffit que je trouve par quoi remplacer le *width et ce sera bon.

+0 -0

J'ai mis à jour l'OP. On peut y trouver les sources des différentes versions des codes-source, ainsi que le résultat visuel de chacune d'elles.

viki53, n'y a-t-il vraiment aucun moyen de revenir à la version A-variante-1 (cf l'OP), et d'ajouter tout simplement un coeff multiplicateur qui permette aux barres de prendre un maximum de place ?

Car pour l'instant c'est cette version qui est la plus proche de ce que je veux faire, étant donné que les largeurs sont cohérentes (mais simplement trop petites :-/).

+0 -0

Salut !

Pour répondre à ta dernière question (ajoutée après édition ?), je pense que la multiplication par un rapport entre la largeur totale de toutes tes barres et la largeur disponible pourrait faire l'affaire, non ?

+0 -0

Edit : je viens de le faire, et les barres sont minuscules :/ J'ai mis à jour l'OP (version A-variante-2).

Lern-X

C'est que ton rapport est l'inverse mathématique de ce que tu veux, je dirais ;)

Ymox

Eh bien j'ai essayé le rapport "largeur du graphe sur largeurs cumulées des barres" et le rapport inverse. Les deux donnent des barres difficilement visibles (vraiment trop petites).

1
return data.x/max_value_x(total_x/width) + "%"; 

data.x , c'est une valeur (des euros, le nombre d'employés, etc. : ça peut être tout et n'importe quoi).

max_value_x , c'est la plus grande valeur data.x.

total_x , c'est la somme de tous les data.x

width, c'est la largeur en pixels du graphe :).

Donc déjà oublie width vu que ça représente 100% et que tu utilises des pourcentages. En gros ça revient à multiplier par un nombre de pixels alors que c'est pas l'unité qui intervient ici.

Normalement pour avoir la largeur de chaque barre tu fais (valeurBarre / totalDesBarres) * 100 (qui donne ), soit (data.x/total_x)*100 si j'ai bien compris tes variables.

Après il faut peut-être vérifier la valeur de ces variables dans le contexte du callback.


[EDIT] Du coup je me rends compte qu'il manquait le *100 depuis le début ^^

Tout dépend si cet espace est défini en pixels ou en pourcentage

Pourcentage

Le calcul est plutôt simple, vu que l'unité est la même

1
2
3
4
5
6
7
var margin = 2; /* Pourcents */
var total_x += margin * (this.array_JSON.length - 1) /* On ajoute un espace pour chaque barre sauf la dernière */

// […]

new_Node.style("width", function (data) { return ((data.x/total_x)*100) + "%"; });
new_Node.style("margin-right", margin + "%"); // Ne pas appliquer à la dernière barre (ou ajuster le calcul de `total_x`)

Pixels

Le calcul devient plus complexe vu qu'il faut tout changer d'unité (donc l'affichage devient fixe, à moins d'intégrer du CSS3 avec calc()).

Ça devrait donner quelque chose comme ça :

1
2
3
4
5
6
7
8
9
var width = $(this.css_selector).width();
var margin = 5; /* Pixels */

// […]

new_Node.style("width", function (data) { return (((data.x/total_x)*width) - margin) + "px"; });
new_Node.style("margin-right", margin + "px");

 // Ne pas appliquer la marge à la dernière barre de préférence pour que ce soit joli
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte