├── example.png ├── LICENSE ├── README.md ├── animationExample.qml ├── Chart.qml └── main.qml /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MichaelVoelkel/ChartJs2QML/HEAD/example.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 ChartJs2QML contributors (starting with Elypson, Michael A. Voelkel, https://github.com/Elypson) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChartJs2QML 2 | This is a QML adaptor for Chart.js 2.9.3 that supports startup animations and tooltips. This way you can integrate charts conveniently (also for commercial products thanks to MIT-license) with your QML-based projects. This also works fine when using Qt with C++ with the desire to display some charts. Feel free to ask questions under issues, if you need help with that. 3 | 4 | ## Getting started 5 | 6 | You can just import Chart.qml and use it with `chartType`, `chartData` and `chartOptions` to generate a chart. For testing, just clone this repository and run 7 | 8 | qmlscene main.qml 9 | 10 | assuming that you have `bin` of Qt in your path variable. Otherwise, start `main.qml` in any other way. The result should look like this: 11 | 12 | ![Output of main.qml on MacOS](example.png) 13 | 14 | ## Animation of changes 15 | 16 | We have added the possibility to change data and animate to changed data. The file `animationExample.qml` can be started to see how it works. Just update the corresponding `chartData` property of `Chart` and then call its function `animateToNewData`. Graphs are not rebuilt but rather data is just updated within Chart.js, which is convenient in terms of resource-usage and performance. You can also define via QML the easing type and duration of the animation. 17 | 18 | ## Notes 19 | 20 | We directly adapt Chart.js of version 2.9.3 and for using data and options, we refer to all the examples that can be found there: https://www.chartjs.org 21 | 22 | For using it with QML, we have changed Chart.js at many places that we have pointed out at the top of Chart.js. Note that the idea for modifications are inspired by Shuirna (https://github.com/shuirna) and their changes were inspired by Julien Wintz. 23 | 24 | Also note that we do not guarantee that this adaptor works. We have not tested all different Chart.js charts. We might or might not extend or modify this adaptor. Feel free to provide pull requests if you would like to improve this adaptor. 25 | 26 | We hope that this little package might help you with your projects! 27 | 28 | All the best, Michael 29 | -------------------------------------------------------------------------------- /animationExample.qml: -------------------------------------------------------------------------------- 1 | /*! 2 | * Elypson's Chart.qml adaptor to Chart.js 3 | * (c) 2020 ChartJs2QML contributors (starting with Elypson, Michael A. Voelkel, https://github.com/Elypson) 4 | * Released under the MIT License 5 | */ 6 | 7 | import QtQuick 2.0 8 | import QtQuick.Window 2.0 9 | import "Chart.js" as Chart 10 | 11 | Window { 12 | id: myWindow 13 | width: 800 14 | height: 800 15 | 16 | function generateData() { 17 | var result = []; 18 | for(var i = 0; i < 10; ++i) { 19 | result[i] = Math.random().toFixed(2); 20 | } 21 | return result; 22 | } 23 | 24 | Chart { 25 | id: chart 26 | chartType: 'bar' 27 | animationEasingType: Easing.Linear 28 | animationDuration: 200 29 | chartData: { 30 | return { 31 | labels: [1,2,3,4,5,6,7,8,9,10], 32 | datasets: [{ 33 | label: 'One dataset', 34 | xAxisId: 'x-axis-1', 35 | yAxisId: 'y-axis-1', 36 | backgroundColor: 'rgba(255, 192, 192, 0.8)', 37 | borderColor: 'rgba(255,0,0,1)', 38 | borderWidth: 1, 39 | data: myWindow.generateData() 40 | }] 41 | } 42 | } 43 | 44 | chartOptions: {return { 45 | maintainAspectRatio: false, 46 | responsive: true, 47 | hoverMode: 'nearest', 48 | intersect: true, 49 | title: { 50 | display: true, 51 | text: 'Chart.js Scatter Chart - Multi Axis' 52 | }, 53 | scales: { 54 | xAxes: [{ 55 | position: 'bottom', 56 | id: 'x-axis-1', 57 | gridLines: { 58 | zeroLineColor: 'rgba(0,0,0,1)' 59 | } 60 | }], 61 | yAxes: [{ 62 | display: true, 63 | position: 'left', 64 | id: 'y-axis-1', 65 | ticks: { 66 | min: 0, 67 | max: 1, 68 | stepSize: 0.1 69 | } 70 | }] 71 | } 72 | } 73 | } 74 | 75 | anchors { 76 | fill: parent 77 | bottomMargin: 50 78 | } 79 | } 80 | Rectangle { 81 | anchors { 82 | horizontalCenter: parent.horizontalCenter 83 | bottom: parent.bottom 84 | topMargin: 5 85 | bottomMargin: 5 86 | } 87 | height: 40 88 | width: parent.width * 0.6 89 | color: "#eee" 90 | border.color: "#999" 91 | 92 | MouseArea { 93 | anchors.fill: parent 94 | onClicked: { 95 | chart.chartData.datasets[0].data = myWindow.generateData(); 96 | chart.animateToNewData(); 97 | } 98 | } 99 | 100 | Text { 101 | anchors.horizontalCenter: parent.horizontalCenter 102 | anchors.verticalCenter: parent.verticalCenter 103 | text: "Randomize data" 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Chart.qml: -------------------------------------------------------------------------------- 1 | /*! 2 | * Elypson's Chart.qml adaptor to Chart.js 3 | * (c) 2020 ChartJs2QML contributors (starting with Elypson, Michael A. Voelkel, https://github.com/Elypson) 4 | * Released under the MIT License 5 | */ 6 | 7 | import QtQuick 2.13 8 | import "Chart.js" as Chart 9 | 10 | Canvas { 11 | id: root 12 | 13 | property var jsChart: undefined 14 | property string chartType 15 | property var chartData 16 | property var chartOptions 17 | property double chartAnimationProgress: 0.1 18 | property var animationEasingType: Easing.InOutExpo 19 | property double animationDuration: 500 20 | property var memorizedContext 21 | property var memorizedData 22 | property var memorizedOptions 23 | property alias animationRunning: chartAnimator.running 24 | 25 | signal animationFinished() 26 | 27 | function animateToNewData() 28 | { 29 | chartAnimationProgress = 0.1; 30 | jsChart.update(); 31 | chartAnimator.restart(); 32 | } 33 | 34 | MouseArea { 35 | id: event 36 | anchors.fill: root 37 | hoverEnabled: true 38 | enabled: true 39 | property var handler: undefined 40 | 41 | property QtObject mouseEvent: QtObject { 42 | property int left: 0 43 | property int top: 0 44 | property int x: 0 45 | property int y: 0 46 | property int clientX: 0 47 | property int clientY: 0 48 | property string type: "" 49 | property var target 50 | } 51 | 52 | function submitEvent(mouse, type) { 53 | mouseEvent.type = type 54 | mouseEvent.clientX = mouse ? mouse.x : 0; 55 | mouseEvent.clientY = mouse ? mouse.y : 0; 56 | mouseEvent.x = mouse ? mouse.x : 0; 57 | mouseEvent.y = mouse ? mouse.y : 0; 58 | mouseEvent.left = 0; 59 | mouseEvent.top = 0; 60 | mouseEvent.target = root; 61 | 62 | if(handler) { 63 | handler(mouseEvent); 64 | } 65 | 66 | root.requestPaint(); 67 | } 68 | 69 | onClicked: { 70 | submitEvent(mouse, "click"); 71 | } 72 | onPositionChanged: { 73 | submitEvent(mouse, "mousemove"); 74 | } 75 | onExited: { 76 | submitEvent(undefined, "mouseout"); 77 | } 78 | onEntered: { 79 | submitEvent(undefined, "mouseenter"); 80 | } 81 | onPressed: { 82 | submitEvent(mouse, "mousedown"); 83 | } 84 | onReleased: { 85 | submitEvent(mouse, "mouseup"); 86 | } 87 | } 88 | 89 | PropertyAnimation { 90 | id: chartAnimator 91 | target: root 92 | property: "chartAnimationProgress" 93 | alwaysRunToEnd: true 94 | to: 1 95 | duration: root.animationDuration 96 | easing.type: root.animationEasingType 97 | onFinished: { 98 | root.animationFinished(); 99 | } 100 | } 101 | 102 | onChartAnimationProgressChanged: { 103 | root.requestPaint(); 104 | } 105 | 106 | onPaint: { 107 | if(root.getContext('2d') != null && memorizedContext != root.getContext('2d') || memorizedData != root.chartData || memorizedOptions != root.chartOptions) { 108 | var ctx = root.getContext('2d'); 109 | 110 | jsChart = new Chart.build(ctx, { 111 | type: root.chartType, 112 | data: root.chartData, 113 | options: root.chartOptions 114 | }); 115 | 116 | memorizedData = root.chartData ; 117 | memorizedContext = root.getContext('2d'); 118 | memorizedOptions = root.chartOptions; 119 | 120 | root.jsChart.bindEvents(function(newHandler) {event.handler = newHandler;}); 121 | 122 | chartAnimator.start(); 123 | } 124 | 125 | jsChart.draw(chartAnimationProgress); 126 | } 127 | 128 | onWidthChanged: { 129 | if(jsChart) { 130 | jsChart.resize(); 131 | } 132 | } 133 | 134 | onHeightChanged: { 135 | if(jsChart) { 136 | jsChart.resize(); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /main.qml: -------------------------------------------------------------------------------- 1 | /*! 2 | * Elypson's Chart.qml adaptor to Chart.js 3 | * (c) 2020 ChartJs2QML contributors (starting with Elypson, Michael A. Voelkel, https://github.com/Elypson) 4 | * Released under the MIT License 5 | */ 6 | 7 | import QtQuick 2.0 8 | import QtQuick.Window 2.0 9 | import "Chart.js" as Chart 10 | 11 | Window 12 | { 13 | id: theWindow 14 | width: 800 15 | height: 800 16 | 17 | function randomScalingFactor() { 18 | return Math.random().toFixed(1); 19 | } 20 | 21 | Item { 22 | id: grid 23 | anchors.fill: parent 24 | 25 | Chart { 26 | chartType: 'scatter' 27 | chartData: { 28 | return { 29 | datasets: [{ 30 | label: 'My First dataset', 31 | xAxisID: 'x-axis-1', 32 | yAxisID: 'y-axis-1', 33 | borderColor: '#ff5555', 34 | backgroundColor: 'rgba(255,192,192,0.3)', 35 | data: [{ 36 | x: theWindow.randomScalingFactor(), 37 | y: theWindow.randomScalingFactor(), 38 | }, { 39 | x: theWindow.randomScalingFactor(), 40 | y: theWindow.randomScalingFactor(), 41 | }, { 42 | x: theWindow.randomScalingFactor(), 43 | y: theWindow.randomScalingFactor(), 44 | }, { 45 | x: theWindow.randomScalingFactor(), 46 | y: theWindow.randomScalingFactor(), 47 | }, { 48 | x: theWindow.randomScalingFactor(), 49 | y: theWindow.randomScalingFactor(), 50 | }, { 51 | x: theWindow.randomScalingFactor(), 52 | y: theWindow.randomScalingFactor(), 53 | }, { 54 | x: theWindow.randomScalingFactor(), 55 | y: theWindow.randomScalingFactor(), 56 | }] 57 | }, { 58 | label: 'My Second dataset', 59 | xAxisID: 'x-axis-1', 60 | yAxisID: 'y-axis-2', 61 | borderColor: '#5555ff', 62 | backgroundColor: 'rgba(192,192,255,0.3)', 63 | data: [{ 64 | x: theWindow.randomScalingFactor(), 65 | y: theWindow.randomScalingFactor(), 66 | }, { 67 | x: theWindow.randomScalingFactor(), 68 | y: theWindow.randomScalingFactor(), 69 | }, { 70 | x: theWindow.randomScalingFactor(), 71 | y: theWindow.randomScalingFactor(), 72 | }, { 73 | x: theWindow.randomScalingFactor(), 74 | y: theWindow.randomScalingFactor(), 75 | }, { 76 | x: theWindow.randomScalingFactor(), 77 | y: theWindow.randomScalingFactor(), 78 | }, { 79 | x: theWindow.randomScalingFactor(), 80 | y: theWindow.randomScalingFactor(), 81 | }, { 82 | x: theWindow.randomScalingFactor(), 83 | y: theWindow.randomScalingFactor(), 84 | }] 85 | }] 86 | }} 87 | chartOptions: {return { 88 | maintainAspectRatio: false, 89 | responsive: true, 90 | hoverMode: 'nearest', 91 | intersect: true, 92 | title: { 93 | display: true, 94 | text: 'Chart.js Scatter Chart - Multi Axis' 95 | }, 96 | scales: { 97 | xAxes: [{ 98 | position: 'bottom', 99 | gridLines: { 100 | zeroLineColor: 'rgba(0,0,0,1)' 101 | } 102 | }], 103 | yAxes: [{ 104 | type: 'linear', // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance 105 | display: true, 106 | position: 'left', 107 | id: 'y-axis-1', 108 | }, { 109 | type: 'linear', // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance 110 | display: true, 111 | position: 'right', 112 | reverse: true, 113 | id: 'y-axis-2', 114 | 115 | // grid line settings 116 | gridLines: { 117 | drawOnChartArea: false, // only want the grid lines for one axis to show up 118 | }, 119 | }], 120 | } 121 | } 122 | } 123 | 124 | anchors { 125 | left: parent.left 126 | top: parent.top 127 | right: parent.horizontalCenter 128 | bottom: parent.verticalCenter 129 | } 130 | } 131 | 132 | Chart { 133 | id: canvasBars 134 | chartType: "bar" 135 | 136 | anchors { 137 | left: parent.horizontalCenter 138 | top: parent.top 139 | right: parent.right 140 | bottom: parent.verticalCenter 141 | } 142 | 143 | chartData: { return { 144 | labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], 145 | datasets: [{ 146 | label: 'Dataset 1', 147 | backgroundColor: '#ff9999', 148 | stack: 'Stack 0', 149 | data: [ 150 | randomScalingFactor(), 151 | randomScalingFactor(), 152 | randomScalingFactor(), 153 | randomScalingFactor(), 154 | randomScalingFactor(), 155 | randomScalingFactor(), 156 | randomScalingFactor() 157 | ] 158 | }, { 159 | label: 'Dataset 2', 160 | backgroundColor: '#9999ff', 161 | stack: 'Stack 0', 162 | data: [ 163 | randomScalingFactor(), 164 | randomScalingFactor(), 165 | randomScalingFactor(), 166 | randomScalingFactor(), 167 | randomScalingFactor(), 168 | randomScalingFactor(), 169 | randomScalingFactor() 170 | ] 171 | }, { 172 | label: 'Dataset 3', 173 | backgroundColor: '#99ff99', 174 | stack: 'Stack 1', 175 | data: [ 176 | randomScalingFactor(), 177 | randomScalingFactor(), 178 | randomScalingFactor(), 179 | randomScalingFactor(), 180 | randomScalingFactor(), 181 | randomScalingFactor(), 182 | randomScalingFactor() 183 | ] 184 | }] 185 | } 186 | } 187 | 188 | chartOptions: { return { 189 | maintainAspectRatio: false, 190 | title: { 191 | display: true, 192 | text: 'Chart.js Bar Chart - Stacked' 193 | }, 194 | tooltips: { 195 | mode: 'index', 196 | intersect: false 197 | }, 198 | responsive: true, 199 | scales: { 200 | xAxes: [{ 201 | stacked: true, 202 | }], 203 | yAxes: [{ 204 | stacked: true 205 | }] 206 | } 207 | } 208 | } 209 | } 210 | 211 | Chart { 212 | id: canvasPie 213 | anchors { 214 | left: parent.left 215 | top: parent.verticalCenter 216 | right: parent.horizontalCenter 217 | bottom: parent.bottom 218 | } 219 | 220 | chartType: "pie" 221 | 222 | chartData: {return { 223 | datasets: [{ 224 | data: [ 225 | theWindow.randomScalingFactor(), 226 | theWindow.randomScalingFactor(), 227 | theWindow.randomScalingFactor(), 228 | theWindow.randomScalingFactor(), 229 | theWindow.randomScalingFactor(), 230 | ], 231 | backgroundColor: [ 232 | '#ffbbbb', 233 | '#ffddaa', 234 | '#ffffbb', 235 | '#bbffbb', 236 | '#bbbbff' 237 | ], 238 | label: 'Dataset 1' 239 | }], 240 | labels: [ 241 | 'Red', 242 | 'Orange', 243 | 'Yellow', 244 | 'Green', 245 | 'Blue' 246 | ] 247 | }} 248 | 249 | chartOptions: {return {maintainAspectRatio: false, responsive: true}} 250 | } 251 | 252 | Chart { 253 | anchors { 254 | left: parent.horizontalCenter 255 | top: parent.verticalCenter 256 | right: parent.right 257 | bottom: parent.bottom 258 | } 259 | chartType: 'line' 260 | 261 | chartData: { return { 262 | labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], 263 | datasets: [{ 264 | label: 'Filled', 265 | fill: true, 266 | backgroundColor: 'rgba(192,222,255,0.3)', 267 | borderColor: 'rgba(128,192,255,255)', 268 | data: [ 269 | theWindow.randomScalingFactor(), 270 | theWindow.randomScalingFactor(), 271 | theWindow.randomScalingFactor(), 272 | theWindow.randomScalingFactor(), 273 | theWindow.randomScalingFactor(), 274 | theWindow.randomScalingFactor(), 275 | theWindow.randomScalingFactor() 276 | ], 277 | }, { 278 | label: 'Dashed', 279 | fill: false, 280 | backgroundColor: 'rgba(0,0,0,0)', 281 | borderColor: '#009900', 282 | borderDash: [5, 5], 283 | data: [ 284 | theWindow.randomScalingFactor(), 285 | theWindow.randomScalingFactor(), 286 | theWindow.randomScalingFactor(), 287 | theWindow.randomScalingFactor(), 288 | theWindow.randomScalingFactor(), 289 | theWindow.randomScalingFactor(), 290 | theWindow.randomScalingFactor() 291 | ], 292 | }, { 293 | label: 'Filled', 294 | backgroundColor: 'rgba(0,0,0,0)', 295 | borderColor: '#990000', 296 | data: [ 297 | theWindow.randomScalingFactor(), 298 | theWindow.randomScalingFactor(), 299 | theWindow.randomScalingFactor(), 300 | theWindow.randomScalingFactor(), 301 | theWindow.randomScalingFactor(), 302 | theWindow.randomScalingFactor(), 303 | theWindow.randomScalingFactor() 304 | ], 305 | fill: false, 306 | }] 307 | } 308 | } 309 | 310 | chartOptions: {return { 311 | maintainAspectRatio: false, 312 | responsive: true, 313 | title: { 314 | display: true, 315 | text: 'Chart.js Line Chart' 316 | }, 317 | tooltips: { 318 | mode: 'index', 319 | intersect: false, 320 | }, 321 | hover: { 322 | mode: 'nearest', 323 | intersect: true 324 | }, 325 | scales: { 326 | xAxes: [{ 327 | display: true, 328 | scaleLabel: { 329 | display: true, 330 | labelString: 'Month' 331 | } 332 | }], 333 | yAxes: [{ 334 | display: true, 335 | scaleLabel: { 336 | display: true, 337 | labelString: 'Value' 338 | } 339 | }] 340 | } 341 | } 342 | } 343 | } 344 | } 345 | } 346 | --------------------------------------------------------------------------------