IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

JavaFX Discussion :

Création composant JavaFX custom de Ligne de Temps


Sujet :

JavaFX

  1. #1
    Membre régulier

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2014
    Messages
    24
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Février 2014
    Messages : 24
    Points : 120
    Points
    120
    Par défaut Création composant JavaFX custom de Ligne de Temps
    Bonjour,

    J'aurais besoin d'un composant de "Ligne de Temps" semblable au Timeline de DVT d'Oracle qui me semble n'existe que pour Java EE.

    Nom : AdvSmoothTimeLine3.png
Affichages : 909
Taille : 39,8 Ko


    Je n'ai pas trouvé ce genre de composant de déjà fait et disponible sur le web pour JavaFX, alors j'envisage de le développer moi-même. Par contre, je n'ai pas d'idée de la démarche à suivre pour ce projet. J'ai bien vue des petits tutoriels en anglais qui montrent comment faire des composants custom, mais qui ne sont rien d'autres que des assemblages de composants préexistant. Et j'aimerais bien pouvoir utiliser mon futur composant dans Scene Builder lorsque je serai rendu a développer l'application qui nécessite cette ligne de temps...

    Bref, si vous connaissez déjà un composant JavaFX de Ligne de temps pré-existant, j'aimerais bien le connaître.

    Sinon, connaissez-vous de la documentation style "marche à suivre" ou autre pour la création de composant JavaFX custom? (pas juste des assemblages de composants...)

    Merci,
    Nome

  2. #2
    Rédacteur/Modérateur

    Avatar de bouye
    Homme Profil pro
    Information Technologies Specialist (Scientific Computing)
    Inscrit en
    Août 2005
    Messages
    6 840
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : Nouvelle-Calédonie

    Informations professionnelles :
    Activité : Information Technologies Specialist (Scientific Computing)
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Août 2005
    Messages : 6 840
    Points : 22 854
    Points
    22 854
    Billets dans le blog
    51
    Par défaut
    Un petit screenshot ou une image pour montrer ce à quoi ça ressemble ?
    Merci de penser au tag quand une réponse a été apportée à votre question. Aucune réponse ne sera donnée à des messages privés portant sur des questions d'ordre technique. Les forums sont là pour que vous y postiez publiquement vos problèmes.

    suivez mon blog sur Développez.

    Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the universe trying to produce bigger and better idiots. So far, the universe is winning. ~ Rich Cook

  3. #3
    Membre régulier

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2014
    Messages
    24
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Février 2014
    Messages : 24
    Points : 120
    Points
    120
    Par défaut
    Grosso modo c'est ce genre de diagramme:

    Nom : AdvSmoothTimeLine3.png
Affichages : 1109
Taille : 39,8 Ko

  4. #4
    Rédacteur/Modérateur

    Avatar de bouye
    Homme Profil pro
    Information Technologies Specialist (Scientific Computing)
    Inscrit en
    Août 2005
    Messages
    6 840
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : Nouvelle-Calédonie

    Informations professionnelles :
    Activité : Information Technologies Specialist (Scientific Computing)
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Août 2005
    Messages : 6 840
    Points : 22 854
    Points
    22 854
    Billets dans le blog
    51
    Par défaut
    Toute la frise est affichee d'une coup et on ne peut pas scroller a droite et a gauche ?
    1. Déjà commence par de finir ton modèle, comment tu va représenter les infos importantes pour ce qui ressemble a un graphe (mais a une seule dimension contrairement aux graphes de l'API) :
      • Les bornes de la frise [date min, date max]. Plus tard tu pourra faire comme les graphe de l'API, essayer d'auto calculer des dates "jolies" qui sont rondes pour que ça ne pique pas les yeux de l'utilisateur.
      • Les écarts des ticks majeurs (ici décades)
      • Les écarts des ticks mineurs (ici toutes les 2 ans)
      • Les événements ponctuels qui semblent contenir les info suivantes :
        • Texte long riche (avec retour a la ligne)
        • Date
        • Coordonnées géographique (Apollo 11)
        • Image (Apollo11, paquet cadeau)
        • Icone de marqueur (W3C)
        • Couleur (plusieurs ont des couleurs différentes)
        • Y a une infobulle sur le marqueur qui dit "Special Event!" (anniversaire)
      • Les plages d'événements qui sont inclus dans la frise:
        • Non court
        • Date de début
        • Date de fin
        • marqueur Date de début
        • marqueur Date de fin
        • Texte Date de début
        • Texte Date de fin

        A voir les les dates de début et de fin peuvent être tout événements gérés comme des événements ponctuels.
    2. Visiblement certaines choses sont des éléments liés a la représentation graphiques (couleur, image, icône, marqueur etc.). A voir si tu veux vraiment les inclure dans le modèle (pourquoi pas) ou si tu préfères passer par des CSS après coup.
    3. Concernant la représentation que peut-on voir ?
      • La base de la frise est un rectangle bleu -> Rectangle ou Region
      • Le cadre WWII est un rectangle orange -> Rectangle ou Region
      • Les ticks sont des lignes -> Path ou Line ou Rectangle ou Region + borders que d'un seul coté dans CSS.
      • Les marqueurs sont des losanges -> Path ou Rectangle + rotation ou SVGPath ou Region + SVG dans CSS.
      • Les marqueurs sont des cercles -> Path ou Rectangle + coins arrondis ou Circle ou SVGPath ou Region + SVG dans CSS.
      • Noter le marqueur dispose d'une element secondaire : une petite ligne qui passe sous la frise (tres visible sur les deux premiers marqueurs) -> Path ou Line ou Rectangle ou Region + borders que d'un seul coté dans CSS.
      • Les marqueurs circulaires sont semi-transparents (on peut voir la bordure du cadre WWII au travers).
      • Les connecteurs sont des lignes -> Path ou Rectangle + coins arrondis ou Circle ou SVGPath ou Region + SVG dans CSS.
      • Les ticks et les connecteurs s'affichent par dessus l'icone W3C -> ça donne une idée sur comment est agencé le contenu.
      • Lest texte simples sont des Text ou des Label
      • Les texte multilignes sont des Text ou des Label configurés en multiligne ou du TextLayout avec de multiples textes dedans.
      • Pour les événements, l'image est toujours a droite du texte long -> un GridPane ou un HBox
      • Pour les événements on a des rectangles colores avec des bords arrondis ou non -> le layout choisi (voir ci-dessus) + bordure et fond et coins arrondis via CSS ou non
      • Pour les événements ils sont semi-transparents (on peut voir un connecteur passer sous l'événement "Boy Scout") -> encore une fois ça donne une idée sur comment est agencé le contenu.
      • Il y a assez d'espace autour de la frise pour éviter que les labels soient coupes pour les dates -> padding dans CSS.
      • Il y a assez d'espace autour de la frise pour éviter que les cadres sortent de l’écran -> va falloir voir le calcul des tailles préférées et minimales.


      PS : Les contrôles fournis dans JavaFX utilisent énormément Region (mème pour dessiner de simples lignes parfois), beaucoup plus souvent que les formes de base telles que Rectangle, Circle ou autres. Region c'est un peu comme le <DIV> en HTML, ça sert a tout faire.
    4. Le positionnement et la mise en page... bon de ce coté la c'est pas trop mon truc mais soit on suppose que les placements sont donnés en dur dans le modèle (événement placé en haut, en bas, vers la droite, vers la gauche) ou que la frise a des règles (event normal et spécial en haut, début fin de plage et événement rouge en dessous), soit il va falloir calculer ça a mano. Tout ca bien sur influera sur les tailles préférées et minimales en plus.


    Commençons par la base le squelette du nouveau composant (note je n'irai pas jusqu'au composant final, je veux juste donner une idée de comment commencer) :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package timeline;
     
    import java.net.URL;
    import javafx.scene.layout.Region;
     
    public class TimelineView extends Region {
     
        public TimelineView() {
            super();
            setId("timelimeView");
            getStyleClass().add("timeline-view");
        }
     
        @Override
        public String getUserAgentStylesheet() {
            final URL url = getClass().getResource("timelineview.css");
            return (url == null) ? null : url.toExternalForm();
        }
    }
    Code CSS : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    .timeline-view {
        /* Pour debuger le controle. */
        -fx-border-color: red;
        -fx-border-width: 5px;
    }

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    package timeline;
     
    import javafx.application.Application;
    import javafx.scene.Scene;
    import javafx.scene.layout.StackPane;
    import javafx.stage.Stage;
     
    public class Main extends Application {
     
        @Override
        public void start(final Stage primaryStage) {
            final TimelineView timelineView = new TimelineView();
            final StackPane root = new StackPane();
            root.getChildren().add(timelineView);        
            final Scene scene = new Scene(root, 600, 600);        
            primaryStage.setTitle("Hello World!");
            primaryStage.setScene(scene);
            primaryStage.show();
        }
     
        public static void main(String[] args) {
            launch(args);
        }    
    }
    Ici le CSS permet de fournir l'apparence par défaut du contrôle. Quand tu utilises ensuite ce controle dans une autre app, tu pourras le customiser davantage via le CSS de cette application.

    Nom : timeline1.jpg
Affichages : 864
Taille : 23,6 Ko
    Merci de penser au tag quand une réponse a été apportée à votre question. Aucune réponse ne sera donnée à des messages privés portant sur des questions d'ordre technique. Les forums sont là pour que vous y postiez publiquement vos problèmes.

    suivez mon blog sur Développez.

    Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the universe trying to produce bigger and better idiots. So far, the universe is winning. ~ Rich Cook

  5. #5
    Rédacteur/Modérateur

    Avatar de bouye
    Homme Profil pro
    Information Technologies Specialist (Scientific Computing)
    Inscrit en
    Août 2005
    Messages
    6 840
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : Nouvelle-Calédonie

    Informations professionnelles :
    Activité : Information Technologies Specialist (Scientific Computing)
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Août 2005
    Messages : 6 840
    Points : 22 854
    Points
    22 854
    Billets dans le blog
    51
    Par défaut
    Commençons par dessiner l'axe :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    44
    public class TimelineView extends Region {
     
        private final Region axis = new Region();
     
        public TimelineView() {
            super();
            setId("timelimeView");
            getStyleClass().add("timeline-view");
            axis.setId("axis");
            axis.getStyleClass().add("axis");
            getChildren().add(axis);
        }
     
        @Override
        public String getUserAgentStylesheet() {
            final URL url = getClass().getResource("timelineview.css");
            return (url == null) ? null : url.toExternalForm();
        }
     
        @Override
        protected void layoutChildren() {
            super.layoutChildren();
            final double width = getWidth();
            final double height = getHeight();
            final Insets insets = getInsets();
            final double areaX = insets.getLeft();
            final double areaY = insets.getTop();
            final double areaW = Math.max(0, width - insets.getLeft() - insets.getRight());
            final double areaH = Math.max(0, height - insets.getTop() - insets.getBottom());
            layoutChildrenInArea(areaX, areaY, areaW, areaH);
        }
     
        private int clamp(final double position) {
            return (int) Math.round(position);
        }
     
        private void layoutChildrenInArea(final double areaX, final double areaY, final double areaW, final double areaH) {
            final int axisX = clamp(areaX);
            final int axisY = clamp(areaY + areaH * 2/3d);
            final int axisW = clamp(areaW);
            final int axisH = clamp(axis.getPrefHeight());
            layoutInArea(axis, axisX, axisY, axisW, axisH, 0, HPos.LEFT, VPos.TOP);
        }
    }

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    .timeline-view {
        -timeline-axis-base: paleturquoise;
        -fx-padding: 15px;
        /* Pour debuger le controle. */
        -fx-border-color: red;
        -fx-border-width: 5px;    
    }
    .timeline-view .axis {
        -fx-border-color: -timeline-axis-base;
        -fx-border-width: 1px;
        -fx-background-color: linear-gradient(from 0% 0% to 0% 100%, transparent, -timeline-axis-base);
        -fx-pref-height: 15px;
    }
    Nom : timeline2.jpg
Affichages : 843
Taille : 26,3 Ko
    Merci de penser au tag quand une réponse a été apportée à votre question. Aucune réponse ne sera donnée à des messages privés portant sur des questions d'ordre technique. Les forums sont là pour que vous y postiez publiquement vos problèmes.

    suivez mon blog sur Développez.

    Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the universe trying to produce bigger and better idiots. So far, the universe is winning. ~ Rich Cook

  6. #6
    Rédacteur/Modérateur

    Avatar de bouye
    Homme Profil pro
    Information Technologies Specialist (Scientific Computing)
    Inscrit en
    Août 2005
    Messages
    6 840
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : Nouvelle-Calédonie

    Informations professionnelles :
    Activité : Information Technologies Specialist (Scientific Computing)
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Août 2005
    Messages : 6 840
    Points : 22 854
    Points
    22 854
    Billets dans le blog
    51
    Par défaut
    Nous allons maintenant rajouter divers propriétés pour les fonctionnalités de base de notre ligne de temps (j'y vais un peu au pif donc je risque de changer en cours de route).

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    public TimelineView() {
            super();
            setId("timelimeView");
            getStyleClass().add("timeline-view");
            axis.setId("axis");
            axis.getStyleClass().add("axis");
            getChildren().add(axis);
            startDateProperty().addListener(timelineChangeListener);
            endDateProperty().addListener(timelineChangeListener);
            tickUnitProperty().addListener(timelineChangeListener);
            tickLengthProperty().addListener(timelineChangeListener);
        }
     
    [...]
     
        private final ReadOnlyObjectWrapper<LocalDate> startDate = new ReadOnlyObjectWrapper<>(this, "startDate", LocalDate.parse("1900-01-01"));
     
        public final LocalDate getStartDate() {
            return startDate.get();
        }
     
        public final void setStartDate(final LocalDate value) throws NullPointerException {
            Objects.requireNonNull(value);
            startDate.set(value);
        }
     
        public final ReadOnlyObjectProperty<LocalDate> startDateProperty() {
            return startDate.getReadOnlyProperty();
        }
     
        private final ReadOnlyObjectWrapper<LocalDate> endDate = new ReadOnlyObjectWrapper<>(this, "endDate", LocalDate.parse("2000-01-01"));
     
        public final LocalDate getEndDate() {
            return endDate.get();
        }
     
        public final void setEndDate(final LocalDate value) throws NullPointerException {
            Objects.requireNonNull(value);
            endDate.set(value);
        }
     
        public final ReadOnlyObjectProperty<LocalDate> endDateProperty() {
            return endDate.getReadOnlyProperty();
        }
     
        private final ReadOnlyObjectWrapper<Period> tickUnit = new ReadOnlyObjectWrapper<>(this, "tickUnit", Period.ofYears(10));
     
        public final Period getTickUnit() {
            return tickUnit.get();
        }
     
        public final void setTickUnit(final Period value) throws NullPointerException {
            Objects.requireNonNull(value);
            tickUnit.set(value);
        }
     
        public final ReadOnlyObjectProperty<Period> tickUnitProperty() {
            return tickUnit.getReadOnlyProperty();
        }
     
     
        private final DoubleProperty tickLength = new SimpleDoubleProperty(this, "tickLength", 15);
     
        public final double getTickLength() {
            return tickLength.get();
        }
     
        public final void setTickLength(final double value) {
            tickLength.set(value);
        }
     
        public final DoubleProperty tickLengthProperty() {
            return tickLength;
        }
     
        private final ChangeListener timelineChangeListener = (observable, oldValue, newValue) -> requestLayout();
    Merci de penser au tag quand une réponse a été apportée à votre question. Aucune réponse ne sera donnée à des messages privés portant sur des questions d'ordre technique. Les forums sont là pour que vous y postiez publiquement vos problèmes.

    suivez mon blog sur Développez.

    Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the universe trying to produce bigger and better idiots. So far, the universe is winning. ~ Rich Cook

  7. #7
    Rédacteur/Modérateur

    Avatar de bouye
    Homme Profil pro
    Information Technologies Specialist (Scientific Computing)
    Inscrit en
    Août 2005
    Messages
    6 840
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : Nouvelle-Calédonie

    Informations professionnelles :
    Activité : Information Technologies Specialist (Scientific Computing)
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Août 2005
    Messages : 6 840
    Points : 22 854
    Points
    22 854
    Billets dans le blog
    51
    Par défaut
    Et nous faisons la mise en page des ticks et des labels sur l'axe :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    public class TimelineView extends Region {
     
        private final Region axis = new Region();
        private final Path ticks = new Path();
        private final Pane tickLabels = new Pane();
     
        public TimelineView() {
            super();
            setId("timelimeView");
            getStyleClass().add("timeline-view");
            axis.setId("axis");
            axis.getStyleClass().add("axis");
            ticks.setId("ticks");
            ticks.getStyleClass().add("ticks");
            tickLabels.setId("tickLabels");
            tickLabels.getStyleClass().add("tick-labels");
            getChildren().addAll(axis, ticks, tickLabels);
            startDateProperty().addListener(timelineChangeListener);
            endDateProperty().addListener(timelineChangeListener);
            tickUnitProperty().addListener(timelineChangeListener);
            tickLengthProperty().addListener(timelineChangeListener);
            widthProperty().addListener(timelineChangeListener);
            heightProperty().addListener(timelineChangeListener);
        }
     
        @Override
        public String getUserAgentStylesheet() {
            final URL url = getClass().getResource("timelineview.css");
            return (url == null) ? null : url.toExternalForm();
        }
     
        @Override
        protected void layoutChildren() {
            super.layoutChildren();
            final double width = getWidth();
            final double height = getHeight();
            final Insets insets = getInsets();
            final double areaX = insets.getLeft();
            final double areaY = insets.getTop();
            final double areaW = Math.max(0, width - insets.getLeft() - insets.getRight());
            final double areaH = Math.max(0, height - insets.getTop() - insets.getBottom());
            layoutChildrenInArea(areaX, areaY, areaW, areaH);
        }
     
        private int clamp(final double position) {
            return (int) Math.round(position);
        }
     
        private void layoutChildrenInArea(final double areaX, final double areaY, final double areaW, final double areaH) {
            final int axisX = clamp(areaX);
            final int axisY = clamp(areaY + areaH * 2 / 3d);
            final int axisW = clamp(areaW);
            final int axisH = clamp(axis.getPrefHeight());
            layoutInArea(axis, axisX, axisY, axisW, axisH, 0, HPos.LEFT, VPos.TOP);
            //
            final Period tickUnit = getTickUnit();
            LocalDate startDate = getStartDate();
            LocalDate endDate = getEndDate();
            if (startDate.isAfter(endDate)) {
                LocalDate tmp = startDate;
                startDate = endDate;
                endDate = tmp;
            }
            final List<LocalDate> calendar = new LinkedList();
            for (LocalDate currentDate = startDate; currentDate.isBefore(endDate) || currentDate.isEqual(endDate); currentDate = currentDate.plus(tickUnit)) {
                calendar.add(currentDate);
            }
            final int tickCount = calendar.size();
            if (tickCount > 0 && ticks.getElements().isEmpty()) {
                final DateTimeFormatter yearExtractor = DateTimeFormatter.ofPattern("YYYY");
                final double tickDistance = axisW / Math.max(1, tickCount - 1);
                final int tickY = axisY + axisH;
                final double tickLength = Math.max(0, getTickLength());
                for (int tickIndex = 0; tickIndex < tickCount; tickIndex++) {
                    final int tickX = clamp(axisX + tickIndex * tickDistance);
                    if (tickLength > 0) {
                        ticks.getElements().add(new MoveTo(tickX, tickY));
                        ticks.getElements().add(new LineTo(tickX, tickY + tickLength));
                    }
                    final LocalDate currentDate = calendar.get(tickIndex);
                    final String text = currentDate.format(yearExtractor);
                    final Text tickLabel = new Text(text);
                    tickLabel.setId("tickLabel");
                    tickLabel.getStyleClass().add("tick-label");
                    tickLabel.setTextOrigin(VPos.TOP);
                    tickLabels.getChildren().add(tickLabel);
                }
                // We need to apply CSS 1st for proper layout.
                applyCss();
                for (int tickIndex = 0; tickIndex < tickCount; tickIndex++) {
                    final int tickX = clamp(axisX + tickIndex * tickDistance);
                    final Text tickLabel = (Text) tickLabels.getChildren().get(tickIndex);
                    final int labelX = clamp(tickX - tickLabel.getBoundsInLocal().getWidth() / 2d);
                    final int labelY = clamp(tickY + tickLength + 5);
                    tickLabel.setLayoutX(labelX);
                    tickLabel.setLayoutY(labelY);
                }
            }
        }
     
        private final ReadOnlyObjectWrapper<LocalDate> startDate = new ReadOnlyObjectWrapper<>(this, "startDate", LocalDate.parse("1900-01-01"));
     
        public final LocalDate getStartDate() {
            return startDate.get();
        }
     
        public final void setStartDate(final LocalDate value) throws NullPointerException {
            Objects.requireNonNull(value);
            startDate.set(value);
        }
     
        public final ReadOnlyObjectProperty<LocalDate> startDateProperty() {
            return startDate.getReadOnlyProperty();
        }
     
        private final ReadOnlyObjectWrapper<LocalDate> endDate = new ReadOnlyObjectWrapper<>(this, "endDate", LocalDate.parse("2000-01-01"));
     
        public final LocalDate getEndDate() {
            return endDate.get();
        }
     
        public final void setEndDate(final LocalDate value) throws NullPointerException {
            Objects.requireNonNull(value);
            endDate.set(value);
        }
     
        public final ReadOnlyObjectProperty<LocalDate> endDateProperty() {
            return endDate.getReadOnlyProperty();
        }
     
        private final ReadOnlyObjectWrapper<Period> tickUnit = new ReadOnlyObjectWrapper<>(this, "tickUnit", Period.ofYears(10));
     
        public final Period getTickUnit() {
            return tickUnit.get();
        }
     
        public final void setTickUnit(final Period value) throws NullPointerException {
            Objects.requireNonNull(value);
            tickUnit.set(value);
        }
     
        public final ReadOnlyObjectProperty<Period> tickUnitProperty() {
            return tickUnit.getReadOnlyProperty();
        }
     
        private final DoubleProperty tickLength = new SimpleDoubleProperty(this, "tickLength", 15);
     
        public final double getTickLength() {
            return tickLength.get();
        }
     
        public final void setTickLength(final double value) {
            tickLength.set(value);
        }
     
        public final DoubleProperty tickLengthProperty() {
            return tickLength;
        }
     
        private final ChangeListener timelineChangeListener = (observable, oldValue, newValue) -> {
            ticks.getElements().clear();
            tickLabels.getChildren().clear();
            requestLayout();
        };
    }
    Code CSS : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    .timeline-view .ticks {
        -fx-stroke-width:2px;
        -fx-stroke-line-cap: butt;
    }
    .timeline-view > * > .tick-label {
        -fx-font-size: 0.8em;
    }

    Nom : timeline3.jpg
Affichages : 898
Taille : 31,1 Ko

    Voila on a déjà un bon début pour la suite. Attention, il y a probablement des bugs ici et la et pas mal d'optimisation a effectuer. Dans mon cas la toute 1ere optimisation que j'ai fait a été d’éviter que les ticks et les labels soient régénérés constamment. Désormais il ne le sont que lorsque la date de début, de fin, la période ou les dimensions du contrôle changent. Le reste du temps on réutilise les ticks et labels existants. Ici par exemple le calendrier (qui sert a creer les labels sur l'axe et a calculer le nombre de ticks) est régénéré a chaque mise en page mais en fait il n'aurait besoin d’être régénéré que lors que la date de début, de fin ou la période change, pas lors que les dimensions sont modifiées.
    Merci de penser au tag quand une réponse a été apportée à votre question. Aucune réponse ne sera donnée à des messages privés portant sur des questions d'ordre technique. Les forums sont là pour que vous y postiez publiquement vos problèmes.

    suivez mon blog sur Développez.

    Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the universe trying to produce bigger and better idiots. So far, the universe is winning. ~ Rich Cook

  8. #8
    Membre régulier

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2014
    Messages
    24
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Février 2014
    Messages : 24
    Points : 120
    Points
    120
    Par défaut
    Ça alors, merci beaucoup Bouye!

    Me reste plus maintenant qu'à digérer tout ça!

  9. #9
    Rédacteur/Modérateur

    Avatar de bouye
    Homme Profil pro
    Information Technologies Specialist (Scientific Computing)
    Inscrit en
    Août 2005
    Messages
    6 840
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : Nouvelle-Calédonie

    Informations professionnelles :
    Activité : Information Technologies Specialist (Scientific Computing)
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Août 2005
    Messages : 6 840
    Points : 22 854
    Points
    22 854
    Billets dans le blog
    51
    Par défaut
    Mais de rien, je viens de rajouter quelques bouts qui manquaient dans le dernier post.

    Ah oui et ici je suis parti sur une approche vectorielle a base de nœud mais il est bien sur tout a fait possible de partir sur une approche bitmap a base de Canvas (et la on dessine dedans comme on ferait en AWT/Swing/Java2D). De même qu'il est possible de découper la chose en sous-partie (ex: les graphiques a 2 axes de l'API gèrent leur axe dans une classe dédiée, de même que les séries du graphique). Juste que pour un début et ce genre de poste c'est plus simple de ne pas trop multiplier les classes.
    Merci de penser au tag quand une réponse a été apportée à votre question. Aucune réponse ne sera donnée à des messages privés portant sur des questions d'ordre technique. Les forums sont là pour que vous y postiez publiquement vos problèmes.

    suivez mon blog sur Développez.

    Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the universe trying to produce bigger and better idiots. So far, the universe is winning. ~ Rich Cook

  10. #10
    Membre régulier

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2014
    Messages
    24
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Février 2014
    Messages : 24
    Points : 120
    Points
    120
    Par défaut
    Citation Envoyé par bouye Voir le message
    Toute la frise est affichee d'une coup et on ne peut pas scroller a droite et a gauche ?
    [LIST=1][*]Déjà commence par de finir ton modèle, comment tu va représenter les infos importantes pour ce qui ressemble a un graphe (mais a une seule dimension contrairement aux graphes de l'API) :
    • Les bornes de la frise [date min, date max]. Plus tard tu pourra faire comme les graphe de l'API, essayer d'auto calculer des dates "jolies" qui sont rondes pour que ça ne pique pas les yeux de l'utilisateur.
    • Les écarts des ticks majeurs (ici décades)
    • Les écarts des ticks mineurs (ici toutes les 2 ans)
    • Les événements ponctuels qui semblent contenir les info suivantes :
      • Texte long riche (avec retour a la ligne)
      • Date
      • Coordonnées géographique (Apollo 11)
      • Image (Apollo11, paquet cadeau)
      • Icone de marqueur (W3C)
      • Couleur (plusieurs ont des couleurs différentes)
      • Y a une infobulle sur le marqueur qui dit "Special Event!" (anniversaire)
    • Les plages d'événements qui sont inclus dans la frise:
      • Non court
      • Date de début
      • Date de fin
      • marqueur Date de début
      • marqueur Date de fin
      • Texte Date de début
      • Texte Date de fin

      A voir les les dates de début et de fin peuvent être tout événements gérés comme des événements ponctuels.

    En fait, l'image que j'ai envoyé n'était que pour donner une idée du genre de graphique que je veux faire.

    Dans le concret, mon objectif est de faire un logiciel pour aider ma mère dans la gestion de la planification des accouplements et des naissances de son élevage canin. Donc grosso-modo je veux pouvoir inscrire les dates des chaleurs qui ont eu lieu et le système va estimer les date des prochaines chaleurs. Également inscrire une date d'accouplement et estimer la date de mise bas. Ma mère aura bientôt trois femelles reproductrices alors les informations des trois femelles seront entremêlée sur la même ligne de temps pour aider à planifier les accouplements pour éviter d'avoir deux portée de chiots en même temps et aussi prévoir des périodes de repos pour les femelles. (Laisser passer 1 ou 2 chaleur entre les grossesses.)

    Ma ligne de temps sera donc divisée en années puis subdivisées en mois et j'aimerais pouvoir intercaler des ticks de semaines qui sont indépendants des mois et années.

    En ce moment, pour me familiariser avec le début de code qui m'a été fourni précédemment, je tente d'ajouter un ScrollPane. ScrollPane qui maintenant présent, je ne comprends pas comment agrandir la zone à l'intérieur du scrollpane. En ce moments, tous les contrôles sont toujours à 600x600(Scene) et ma ligne de temps (que j'ai étirée en lui harcodant une largeur de 1200px) se fait dessiner dans le vide à droite en dehors de la Scene et le scrollbar horizontal ne s'ajuste à rien... J'ai fait des tentatives avec setPrefSize et setPrefViewSize et d'autres sur le ScrollPane et le StackPane sans succès...

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @Override
        public void start(final Stage primaryStage) {
     
            final StackPane root = new StackPane();
            final TimeLineView timelineView = new TimeLineView();
     
            root.getChildren().add(timelineView);
     
            ScrollPane sp = new ScrollPane();
            sp.setHbarPolicy(ScrollBarPolicy.ALWAYS);   
            sp.setFitToHeight(true);
            sp.setFitToWidth(true);
     
            sp.setContent(root);
     
            final Scene scene = new Scene(sp, 600, 600);
            primaryStage.setTitle("Hello World!");
            primaryStage.setScene(scene);
            primaryStage.show();
     
        }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    private void layoutChildrenInArea(final double areaX, final double areaY, final double areaW, final double areaH) {
            final int axisX = clamp(areaX);
            final int axisY = clamp(areaY + areaH * 2 / 3d);
    //        final int axisW = clamp(areaW);
            final int axisW = clamp(1200d);
            final int axisH = clamp(axis.getPrefHeight());
            layoutInArea(axis, axisX, axisY, axisW, axisH, 0, HPos.LEFT, VPos.TOP);
     
            (...)
     
            if (tickCount > 0 && ticks.getElements().isEmpty()) {
                final DateTimeFormatter yearExtractor = DateTimeFormatter.ofPattern("MMMM");
                final double tickDistance = axisW / Math.max(1, tickCount - 1);
     
            (...)     
     
            }
    private final ReadOnlyObjectWrapper<LocalDate> startDate = new ReadOnlyObjectWrapper<>(this, "startDate", LocalDate.parse("2016-01-01"));
     
    private final ReadOnlyObjectWrapper<LocalDate> endDate = new ReadOnlyObjectWrapper<>(this, "endDate", LocalDate.parse("2018-01-01"));
     
        private final ReadOnlyObjectWrapper<Period> tickUnit = new ReadOnlyObjectWrapper<>(this, "tickUnit", Period.ofMonths(1));

  11. #11
    Membre régulier

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2014
    Messages
    24
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Février 2014
    Messages : 24
    Points : 120
    Points
    120
    Par défaut
    Bon, je viens de réussir à faire fonctionner le ScrollPane et l'insérant celui-ci plutôt dans un VBox plutot que de mettre le StackPane dans le ScrollPane...

  12. #12
    Membre régulier

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2014
    Messages
    24
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Février 2014
    Messages : 24
    Points : 120
    Points
    120
    Par défaut
    Je viens de revenir au StackPane et ça fonctionne...

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
     @Override
        public void start(final Stage primaryStage) {
     
            final VBox box = new VBox();
            final StackPane stackP = new StackPane();
     
            final TimeLineView timelineView = new TimeLineView();
     
            ScrollPane sp = new ScrollPane();
     
    //        box.getChildren().addAll(sp);
            stackP.getChildren().addAll(sp);
     
     //*       VBox.setVgrow(sp, Priority.ALWAYS);
     
     //       box.setPrefSize(1200, 600);
            stackP.setPrefSize(1200, 600);
     
    //        sp.setHmax(1200);
            sp.setPrefSize(600, 600);
            sp.setContent(timelineView);
     
            //final Scene scene = new Scene(box, 600, 600);
            final Scene scene = new Scene(stackP, 600, 600);   
            primaryStage.setTitle("Hello World!");
            primaryStage.setScene(scene);
            primaryStage.show();
     
        }

  13. #13
    Rédacteur/Modérateur

    Avatar de bouye
    Homme Profil pro
    Information Technologies Specialist (Scientific Computing)
    Inscrit en
    Août 2005
    Messages
    6 840
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : Nouvelle-Calédonie

    Informations professionnelles :
    Activité : Information Technologies Specialist (Scientific Computing)
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Août 2005
    Messages : 6 840
    Points : 22 854
    Points
    22 854
    Billets dans le blog
    51
    Par défaut
    Vu que tu vas en quelques sorte avoir un défilement infini avec les dates affichées qui vont changer lorsque tu scroll, moi je serai plutot parti sur une ScrollBar qui lorsque tu changes sa valeur, change la "vue" sur la ligne de temps.

    Nom : timelineviewconcept.png
Affichages : 924
Taille : 127,8 Ko


    Ici minDateView et maxDateView sont une vue restreinte (on ne va pas gérer ou afficher l’intégralité des événements enregistrés dans la DB / le calendrier). Le composant a une zone de travail plus large que sa zone d'affichage ce qui permet de faire apparaître des événements qui sont hors-champs (et apparaissent coupés grace a la zone de clip). Lorsque l'utilisateur défile "trop" a droite ou a gauche l'intervalle [minDateView, maxDateView] devient invalide et il devient nécessaire de récupérer a nouveau tous les events dispo pour le nouvel intervalle pour reconstruire l'affichage. Si le défilement est "minimal" on ne fait pas de requêtes sur la DB / le calendrier et on se contente de faire scroller l'affichage dans un sens ou l'autre (translateX). Si on scroll sur les extrémités, alors on sort des dates min max contenus dans la DB / le calendrier mais si on est en scroll infini on continue de generer des dates affichees dans un sens ou un autre meme si aucun event ne s'affiche.

    Ma ligne de temps sera donc divisée en années puis subdivisées en mois et j'aimerais pouvoir intercaler des ticks de semaines qui sont indépendants des mois et années.
    A ce moment la il va sans doute falloir gérer une sorte de niveau de zoom / mode d'affichage sur la frise et un level of detail je pense qui fait que lorsque tu es zoom en années ce sont les décades qui servent de tick, lorsque tu affiches juste 2-3 ans ce sont les mois, 2-8 mois ce sont les semaines, etc... donc au lieu d'avoir un nombre de ticks qui dépend de la période (comme moi) plutôt un nombre de ticks entier (genre 10 entre les deux extrémités) et on calcule la période derrière pour l'affichage.
    Merci de penser au tag quand une réponse a été apportée à votre question. Aucune réponse ne sera donnée à des messages privés portant sur des questions d'ordre technique. Les forums sont là pour que vous y postiez publiquement vos problèmes.

    suivez mon blog sur Développez.

    Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the universe trying to produce bigger and better idiots. So far, the universe is winning. ~ Rich Cook

  14. #14
    Membre régulier

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2014
    Messages
    24
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Février 2014
    Messages : 24
    Points : 120
    Points
    120
    Par défaut
    Salut Bouye,
    Merci de continuer à m'apporter ton aide, c'est très apprécié!

    Je n'avais pas encore réfléchis à la question de limiter ou non la longueur de la ligne de temps. Je pensais, a priori, avoir dans le logiciel, des paramètres de début et de fin à considérer. Mais je trouve ton idée de minmaxDateView très bonne.

    Par contre, pour l'idée de zoom, c'est vrai que dans un monde idéal ce serait parfait, sauf que je n'aurai jamais à visualiser de grandes sections de la ligne de temps dans une même vision. Je pense que de pouvoir afficher entre ½ année à une année de la règle (selon la largeur de l'écran du poste) sera suffisant puisque les événements seront espacé de l'ordre de grandeur du mois. Je veux avoir les ticks de semaine simplement pour mieux visualiser dans quelle semaine du mois sera l'anticipation de la chaleur d'une femelle où de son accouchement.

    Je laisse en plan, pour l'instant, l'idée d'un composant javafx général et paramétrable dans le but de le distribuer et que d'autres puisse en profiter, car c'est déjà un bon morceau pour moi de simplement le développer pour mon contexte d'utilisation.

    Il me reste quelques fignolage à faire sur la règle de temps graduée avant de passer à l'affichage des événements. Et je dois réfléchir à une ébauche de structure de donnée et de classe pour fournir les données d'événements de bulles au contrôle.

    Voilà ce à quoi ressemble ma règle de temps au moment d'écrire ces lignes. Je n'ai pas décidé encore si j'ajoute une troisième épaisseur graduée pour les années, ou si je positionne les ticks avec les labels de dates sous la règle comme tu me l'avais placé au début en t'inspirant de l'image d'exemple que j'avais fourni.

    Nom : TimeLineView002.jpg
Affichages : 855
Taille : 34,0 Ko

  15. #15
    Rédacteur/Modérateur

    Avatar de bouye
    Homme Profil pro
    Information Technologies Specialist (Scientific Computing)
    Inscrit en
    Août 2005
    Messages
    6 840
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : Nouvelle-Calédonie

    Informations professionnelles :
    Activité : Information Technologies Specialist (Scientific Computing)
    Secteur : Agroalimentaire - Agriculture

    Informations forums :
    Inscription : Août 2005
    Messages : 6 840
    Points : 22 854
    Points
    22 854
    Billets dans le blog
    51
    Par défaut
    Bien !

    Le fait de devoir se resteindre sur une vue c'est bien sur pour éviter de plomber la mémoire avec des tas de données mais aussi le Scenegraph avec des tonnes de nœuds. Ce qui finira par arriver a la longue lorsque l'application sera bien rempli et aura été utilise sur plusieurs années quand la BD/le fichier de sauvegarde contiendront beaucoup d’événements. Évidement ce genre d'optimisation peut être faite après coup mais peut t'obliger a restructurer tout le controle.
    Merci de penser au tag quand une réponse a été apportée à votre question. Aucune réponse ne sera donnée à des messages privés portant sur des questions d'ordre technique. Les forums sont là pour que vous y postiez publiquement vos problèmes.

    suivez mon blog sur Développez.

    Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the universe trying to produce bigger and better idiots. So far, the universe is winning. ~ Rich Cook

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. [Talend] Création composant - Ajout d'une "ligne" en sortie
    Par hoshizora dans le forum Développement de jobs
    Réponses: 5
    Dernier message: 23/05/2012, 10h36
  2. Cherche exemple création composant visuel
    Par bertin dans le forum Composants VCL
    Réponses: 1
    Dernier message: 02/08/2005, 16h14
  3. [C#] OnPaint(PaintEventArgs e) sur création Composant
    Par pc152 dans le forum Windows Forms
    Réponses: 4
    Dernier message: 26/08/2004, 16h13
  4. Création composant et BD
    Par gibet_b dans le forum Composants VCL
    Réponses: 6
    Dernier message: 07/07/2004, 15h03
  5. Création d'une connexion en ligne de commande
    Par Drahu dans le forum MS SQL Server
    Réponses: 5
    Dernier message: 10/05/2004, 15h19

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo