Dans un précédent billet, nous avions vu comment créer une courbe en QML depuis une liste de points connus.
Je vous propose maintenant de voir comment créer une courbe dynamiquement, c'est-à-dire en exploitant, par exemple, une liste de coordonnées depuis une base de données ou d'un calcul.
En nous inspirant du premier billet, nous allons créer la fenêtre, le canvas et les différents éléments qui nous permettront de créer notre courbe ou d'interagir avec celle-ci.
Voici ce que cela donnerait :
Code qml : | Sélectionner tout |
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 | import QtQuick 2.5 import QtQuick.Window 2.2 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.2 Window { visible: true width: 800; height: 500 x: 100 ; y: 100 title: "Create curve with QML" Rectangle{ id: root anchors.fill: parent ColumnLayout { id: grid anchors.fill: parent RowLayout { id: control Layout.fillWidth: true implicitHeight: 25 Button { text: "Trace" } Button { text: "Efface" } Slider { id: slider value: 0 maximumValue: 20 } Item { Layout.fillWidth: true } Label { id: posiX } Label { id: posiY } Label { id: posiX2 } } Rectangle { id: area Layout.fillHeight: true Layout.fillWidth: true color: "lightgray" Canvas { id: canvas anchors.fill: parent transform: Rotation { origin.x: area.x; origin.y: area.height/2; angle: 180; axis { x: 1; y: 0; z: 0 }} property int origX: 0 property int origY: 0 property int maxX: width - origX property int maxY: height - origY property alias pointCurve: pointCurve /* c'est ici que ça commence à devenir intéressant. Comme précédemment nous allons créer au démarrage de l'application nos axes, mais nous n'irons pas plus loin.*/ onPaint: { getContext("2d") /* getContext("2d") est nécessaire pour indiquer dans quel type de context nous nous trouvons. Ici on déclare donc un Context2D (http://doc.qt.io/qt-5/qml-qtquick-context2d-members.html) qui a notamment deux méthodes qui nous intéresseront tout particulièrement : - beginPath() - closePath() Entre ces deux méthodes, vous pouvez créer comme bon vous semble différents chemins et y appliquer un style unique (couleur, épaisseur du trait...) . Pour appliquer d'autres styles il faudra redéclarer un nouveau Path (chemin en anglais).*/ /* Trace Axes */ context.beginPath() context.lineWidth = 2 context.moveTo(origX, origY) // permet de se déplacer sans tracer de points context.lineTo(maxX, origY) // permet de tracer une ligne context.moveTo(origX, origY) context.lineTo(origX, maxY) context.strokeStyle = Qt.rgba(0,0,0); context.stroke() context.closePath() } ListModel{id: pointCurve} } } } } } |
À ce stade vous devriez avoir quelque chose qui ressemble à ceci :
Nous allons maintenant créer notre courbe.
Nous allons tout d'abord compléter nos composants Button et Slider afin de permettre l'interaction avec notre Canvas. Commençons par remplir notre ListModel des points à tracer :
Code qml : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | Button { text: "Trace" onClicked: { for (var i=0; i<=20; i++){ pointCurve.append({"x":i*20, "y":Math.pow(i,2)}) } canvas.traceCurve() // nous verrons cette fonction un peu plus bas. } } |
Puis la gestion de l'effacement de notre courbier :
Code qml : | Sélectionner tout |
1 2 3 4 5 6 7 | Button { text: "Efface" onClicked: { slider.value = 0 canvas.clear() // Comme pour canvas.traceCurve() nous verrons bientôt cette fonction. } } |
Et enfin notre Slider qui nous permettra d'afficher un curseur :
Code qml : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | Slider { id: slider value: 0 maximumValue: 20 onValueChanged: { if (value !== 0)canvas.point(root.valueX*20, Math.pow(root.valueX,2)) canvas.traceCurve() } } |
Et voici enfin le moment tant attendu : le traçage de la courbe.
Pour cela nous allons implémenter la fonction suivante :
Code qml : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | function traceCurve() { if (context){ context.beginPath() // comme précédemment nous créons un nouveau Path context.lineWidth = 1 for (var i = 0; i < pointCurve.count; i++) { // Pour chaque élément de notre ListModel, nous créons une nouvelle ligne var posiX = origX + pointCurve.get(i).x var posiY = origY + pointCurve.get(i).y context.lineTo(posiX, posiY) } context.moveTo(origX, origY) context.strokeStyle = Qt.rgba(0,0,1); context.stroke() // Nous appliquons le style voulu context.closePath() // Nous fermons notre chemin } requestPaint() // et nous lançons la requête de rendu |
Le principe pour tracer le curseur (fonction point () utilisée dans le Slider) est sensiblement le même.
Pour effacer le contenu de notre Canvas il suffit d'effacer le contenu de notre ListModel, faire un reset du context et redonner l'ordre de tracer les nouveaux éléments : c'est à dire rien du tout.
Code qml : | Sélectionner tout |
1 2 3 4 5 6 7 | function clear(){ if (context){ pointCurve.clear() context.reset() requestPaint() } } |
Voici ce que donne le code complet :
Code qml : | Sélectionner tout |
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 | import QtQuick 2.5 import QtQuick.Window 2.2 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.2 Window { visible: true width: 800; height: 500 x: 100 ; y: 100 title: "Create curve with QML" Rectangle{ id: root anchors.fill: parent property alias valueX: slider.value ColumnLayout { id: grid anchors.fill: parent RowLayout { id: control Layout.fillWidth: true implicitHeight: 25 Button { text: "Trace" onClicked: { for (var i=0; i<=20; i++){ pointCurve.append({"x":i*20, "y":Math.pow(i,2)}) } canvas.traceCurve() } } Button { text: "Efface" onClicked: { slider.value = 0 canvas.clear() } } Slider { id: slider value: 0 maximumValue: 20 onValueChanged: { if (value !== 0)canvas.point(root.valueX*20, Math.pow(root.valueX,2)) canvas.traceCurve() } } Item { Layout.fillWidth: true } Label { id: posiX text: "x : " + (root.valueX*20).toFixed(2) } Label { id: posiY text: "y : " + Math.pow(root.valueX,2).toFixed(2) } Label { id: posiX2 } } Rectangle { id: area Layout.fillHeight: true Layout.fillWidth: true color: "lightgray" Canvas { id: canvas anchors.fill: parent transform: Rotation { origin.x: area.x; origin.y: area.height/2; angle: 180; axis { x: 1; y: 0; z: 0 }} property int origX: 0 property int origY: 0 property int maxX: width - origX property int maxY: height - origY property alias pointCurve: pointCurve function clear(){ if (context){ pointCurve.clear() context.reset() requestPaint() } } function point(q, h){ context.reset() context.beginPath() context.lineWidth = 1 context.moveTo(origX, h+origY) context.lineTo(maxX, h+origY) context.moveTo(q+origX, origY) context.lineTo(q+origX, maxY) context.strokeStyle = Qt.rgba(1,0,0); context.stroke() context.closePath() requestPaint() } function traceCurve() { if (context){ context.beginPath() context.lineWidth = 1 for (var i = 0; i < pointCurve.count; i++) { var posiX = origX + pointCurve.get(i).x var posiY = origY + pointCurve.get(i).y context.lineTo(posiX, posiY) } context.moveTo(origX, origY) context.strokeStyle = Qt.rgba(0,0,1); context.stroke() context.closePath() } requestPaint() } onPaint: { getContext("2d") /* Trace Axes */ context.beginPath() context.lineWidth = 2 context.moveTo(origX, origY) context.lineTo(maxX, origY) context.moveTo(origX, origY) context.lineTo(origX, maxY) context.strokeStyle = Qt.rgba(0,0,0); context.stroke() context.closePath() } ListModel{id: pointCurve} } } } } } |