diff --git a/deployment.pri b/deployment.pri new file mode 100644 index 0000000..265ce71 --- /dev/null +++ b/deployment.pri @@ -0,0 +1,13 @@ +unix:!android { + isEmpty(target.path) { + qnx { + target.path = /tmp/$${TARGET}/bin + } else { + target.path = /opt/$${TARGET}/bin + } + export(target.path) + } + INSTALLS += target +} + +export(INSTALLS) diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..9e60486 --- /dev/null +++ b/main.cpp @@ -0,0 +1,14 @@ +#include +#include + +int main(int argc, char *argv[]) +{ + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QGuiApplication app(argc, argv); + + QQmlApplicationEngine engine; + engine.addImportPath(QStringLiteral("qml")); + engine.load(QUrl(QLatin1String("qrc:/qml/main.qml"))); + + return app.exec(); +} diff --git a/qml.qrc b/qml.qrc new file mode 100644 index 0000000..8d4d67f --- /dev/null +++ b/qml.qrc @@ -0,0 +1,10 @@ + + + qml/firmata/PortSelector.qml + qml/main.qml + qml/QChartGallery.qml + qml/QChartGallery.js + qml/jbQuick/Charts/QChart.js + qml/jbQuick/Charts/QChart.qml + + diff --git a/qml/QChartGallery.js b/qml/QChartGallery.js new file mode 100644 index 0000000..ebdee55 --- /dev/null +++ b/qml/QChartGallery.js @@ -0,0 +1,136 @@ +// QChartGallery.js --- +// +// Author: Julien Wintz +// Created: Thu Feb 13 23:43:13 2014 (+0100) +// Version: +// Last-Updated: +// By: +// Update #: 13 +// + +// Change Log: +// +// + +// ///////////////////////////////////////////////////////////////// +// Line Chart Data Sample +// ///////////////////////////////////////////////////////////////// + +var ChartLineData = { + labels: ["","","","","","","", "", "", "", "", "", "", "", ""], + datasets: [{ + fillColor: "rgba(220,220,220,0.5)", + strokeColor: "rgba(220,220,220,1)", + pointColor: "rgba(220,220,220,1)", + pointStrokeColor: "#ffffff", + data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] + }, + { + fillColor: "rgba(20,220,220,0.5)", + strokeColor: "rgba(20,220,220,1)", + pointColor: "rgba(20,220,220,1)", + pointStrokeColor: "#ffffff", + data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] + } + ] +} + + +// ///////////////////////////////////////////////////////////////// +// Polar Chart Data Sample +// ///////////////////////////////////////////////////////////////// + +var ChartPolarData = [{ + value: 30, + color: "#D97041" + }, { + value: 90, + color: "#C7604C" + }, { + value: 24, + color: "#21323D" + }, { + value: 58, + color: "#9D9B7F" + }, { + value: 82, + color: "#7D4F6D" + }, { + value: 8, + color: "#584A5E" +}] + +// ///////////////////////////////////////////////////////////////// +// Radar Chart Data Sample +// ///////////////////////////////////////////////////////////////// + +var ChartRadarData = { + labels: ["Eating","Drinking","Sleeping","Designing","Coding","Partying","Running"], + datasets: [{ + fillColor: "rgba(220,220,220,0.5)", + strokeColor: "rgba(220,220,220,1)", + pointColor: "rgba(220,220,220,1)", + pointStrokeColor: "#fff", + data: [65,59,90,81,56,55,40] + }, { + fillColor: "rgba(151,187,205,0.5)", + strokeColor: "rgba(151,187,205,1)", + pointColor: "rgba(151,187,205,1)", + pointStrokeColor: "#fff", + data: [28,48,40,19,96,27,100] + } + ] +} + +// ///////////////////////////////////////////////////////////////// +// Pie Chart Data Sample +// ///////////////////////////////////////////////////////////////// + +var ChartPieData = [{ + value: 30, + color: "#F38630" + }, { + value: 50, + color: "#E0E4CC" + }, { + value: 100, + color: "#69D2E7" +}] + +// ///////////////////////////////////////////////////////////////// +// Doughnut Chart Data Sample +// ///////////////////////////////////////////////////////////////// + +var ChartDoughnutData = [{ + value: 30, + color: "#F7464A" + }, { + value: 50, + color: "#E2EAE9" + }, { + value: 100, + color: "#D4CCC5" + }, { + value: 40, + color: "#949FB1" + }, { + value: 120, + color: "#4D5360" +}] + +// ///////////////////////////////////////////////////////////////// +// Bar Chart Data Sample +// ///////////////////////////////////////////////////////////////// + +var ChartBarData = { + labels: ["January","February","March","April","May","June","July"], + datasets: [{ + fillColor: "rgba(220,220,220,0.5)", + strokeColor: "rgba(220,220,220,1)", + data: [65,59,90,81,56,55,40] + }, { + fillColor: "rgba(151,187,205,0.5)", + strokeColor: "rgba(151,187,205,1)", + data: [28,48,40,19,96,27,100] + }] +} diff --git a/qml/QChartGallery.qml b/qml/QChartGallery.qml new file mode 100644 index 0000000..1e42ba4 --- /dev/null +++ b/qml/QChartGallery.qml @@ -0,0 +1,182 @@ +/* QChartGallery.qml --- + * + * Author: Julien Wintz + * Created: Thu Feb 13 23:41:59 2014 (+0100) + * Version: + * Last-Updated: Fri Feb 14 12:58:42 2014 (+0100) + * By: Julien Wintz + * Update #: 5 + */ + +/* Change Log: + * + */ + +import QtQuick 2.7 +import QtQuick.Controls 2.0 +import QtQuick.Layouts 1.3 +import jbQuick.Charts 1.0 + + + +import "QChartGallery.js" as ChartsData + +Item { + property var chart_line_data: ChartsData + property alias chart_line: chart_line + + property int chart_width: parent.width; + property int chart_height: parent.height; + property int chart_spacing: 20; + property int text_height: 80; + property int row_height: 8; + + Rectangle { + + anchors.fill: parent + + color: "#ffffff"; + width: chart_width + chart_spacing; + height: chart_height + chart_spacing + row_height + text_height; + + // ///////////////////////////////////////////////////////////////// + // Header + // ///////////////////////////////////////////////////////////////// + Rectangle { + + id: button; + visible: true + + anchors.top: parent.top; + anchors.right: parent.right; + anchors.rightMargin: 10 + + width: 100; + height: 32; + + color: "#2d91ea"; + radius: 8; + + Text { + anchors.centerIn: parent; + color: "#ffffff"; + text: "Repaint"; + font.bold: true; + } + + MouseArea { + anchors.fill: parent; + onPressed: { + button.color = "#1785e6" + } + onReleased: { + button.color = "#2d91ea" + chart_line.repaint(); + } + } + } + + // ///////////////////////////////////////////////////////////////// + // Body + // ///////////////////////////////////////////////////////////////// + + + + Grid { + + id: layout; + + anchors.top: parent.top + anchors.left: parent.left + width: parent.width; + height: parent.height - button.height; + + columns: 1; + spacing: chart_spacing; + + Chart { + id: chart_line; + width: parent.width; + height: parent.height; + chartAnimated: true; + chartAnimationEasing: Easing.Linear; + chartAnimationDuration: 0; + chartData: ChartsData.ChartLineData; + chartType: Charts.ChartType.LINE; + chartOptions: ChartsData.ChartLineOption; + + // Rectangle{ + // anchors.fill:parent + // color: "black" + // opacity: 0.2 + // } + + } + } + + + + +// for test + RowLayout { + visible: false + id: inputField + anchors.left: layout.left + anchors.top: layout.bottom + + height: 30 + spacing: 5 + Button { + id: btnStop + height: parent.height + text: "Stop" + onClicked:{ + if( btnStop.text == "Stop"){ + + timer.stop() + btnStop.text = "Start" + } + else{ + btnStop.text = "Stop" + timer.start() + } +// console.log(ChartsData.ChartLineData["datasets"][0]["data"]) +// ChartsData.ChartLineData["datasets"][0]["data"].shift() +// ChartsData.ChartLineData["datasets"][0]["data"].push(inputText.text) +// chart_line.repaint() + } + } + + Button { + id: btnAdd + height: parent.height + text: "Add" + onClicked:{ +// console.log(ChartsData.ChartLineData["datasets"][0]["data"]) +// ChartsData.ChartLineData["datasets"][0]["data"].shift() +// ChartsData.ChartLineData["datasets"][0]["data"].push(inputText.text) +// chart_line.repaint() + } + } + Label { + height: parent.height + text: "Data" + verticalAlignment: Text.AlignVCenter + anchors.fill: parent.Center + // Rectangle{ + // anchors.fill:parent + // color: "black" + // } + } + TextField{ + id: inputText + height: parent.height + text: "30" + } + } + + } + +} + + diff --git a/qml/firmata/PortSelector.qml b/qml/firmata/PortSelector.qml new file mode 100644 index 0000000..448e62d --- /dev/null +++ b/qml/firmata/PortSelector.qml @@ -0,0 +1,59 @@ +import QtQuick 2.0 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.0 +import Firmata 1.0 + +RowLayout { + signal dataReceived(int rawValue) + + function openPort(portName) { + firmata.backend.device = portName; + } + function closePort(){ + firmata.backend.device = ""; + } + + Firmata{ + id: firmata + backend: SerialFirmata{ + baudRate: 115200 + } + AnalogPin { + channel: 7 + pin:7 + onSampled: { + dataReceived(rawValue) + } + + + } + } + + ComboBox { + id: cmbPortName + model: SerialPortList + textRole: "name" + } + + Button{ + id: btnOpen + text: "Open" + onClicked: { + if( text == "Open" ){ + text = "Close" + openPort(cmbPortName.currentText) + } + else { + text = "Open" + closePort() + } + + text: "Close" + } + } + + Label { + text: firmata.statusText + } +} + diff --git a/qml/firmata/Scope.qml b/qml/firmata/Scope.qml new file mode 100644 index 0000000..c25f04b --- /dev/null +++ b/qml/firmata/Scope.qml @@ -0,0 +1,69 @@ +import QtQuick 2.5 + +Canvas { + id: canvas + + property color background: "#000000" + property color foreground: "#27ae60" + property int bufferSize: 100 + + Component.onCompleted: { + internal.resizeBuffer(bufferSize); + } + + onBufferSizeChanged: { + internal.resizeBuffer(bufferSize); + } + + onPaint: { + var ctx = canvas.getContext('2d'); + var i = internal.pos; + var yscale = (canvas.height-1); + var xscale = canvas.width / internal.samples.length + + ctx.fillStyle = canvas.background; + ctx.strokeStyle = canvas.foreground; + + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.beginPath(); + + ctx.moveTo(0, canvas.height - 1 - (yscale * internal.samples[0])); + for(var x=1;x", + scaleFontFamily: "'Arial'", + scaleFontSize: 12, + scaleFontStyle: "normal", + scaleFontColor: "#666", + scaleShowLabelBackdrop: true, + scaleBackdropColor: "rgba(255,255,255,0.75)", + scaleBackdropPaddingY: 2, + scaleBackdropPaddingX: 2, + segmentShowStroke: true, + segmentStrokeColor: "#fff", + segmentStrokeWidth: 2, + animation: true, + animationSteps: 100, + animationEasing: "easeOutBounce", + animateRotate: true, + animateScale: false, + onAnimationComplete: null + }; + + var config = (options) ? mergeChartConfig(chart.PolarArea.defaults,options) : chart.PolarArea.defaults; + + return new PolarArea(data,config,context); + }; + +// ///////////////////////////////////////////////////////////////// +// Radar helper +// ///////////////////////////////////////////////////////////////// + + this.Radar = function(data,options) { + + chart.Radar.defaults = { + scaleOverlay: false, + scaleOverride: false, + scaleSteps: null, + scaleStepWidth: null, + scaleStartValue: null, + scaleShowLine: true, + scaleLineColor: "rgba(0,0,0,.1)", + scaleLineWidth: 1, + scaleShowLabels: false, + scaleLabel: "<%=value%>", + scaleFontFamily: "'Arial'", + scaleFontSize: 12, + scaleFontStyle: "normal", + scaleFontColor: "#666", + scaleShowLabelBackdrop: true, + scaleBackdropColor: "rgba(255,255,255,0.75)", + scaleBackdropPaddingY: 2, + scaleBackdropPaddingX: 2, + angleShowLineOut: true, + angleLineColor: "rgba(0,0,0,.1)", + angleLineWidth: 1, + pointLabelFontFamily: "'Arial'", + pointLabelFontStyle: "normal", + pointLabelFontSize: 12, + pointLabelFontColor: "#666", + pointDot: true, + pointDotRadius: 3, + pointDotStrokeWidth: 1, + datasetStroke: true, + datasetStrokeWidth: 2, + datasetFill: true, + animation: true, + animationSteps: 60, + animationEasing: "easeOutQuart", + onAnimationComplete: null + }; + + var config = (options)? mergeChartConfig(chart.Radar.defaults,options) : chart.Radar.defaults; + + return new Radar(data,config,context); + }; + +// ///////////////////////////////////////////////////////////////// +// Pie helper +// ///////////////////////////////////////////////////////////////// + + this.Pie = function(data,options) { + + chart.Pie.defaults = { + segmentShowStroke: true, + segmentStrokeColor: "#fff", + segmentStrokeWidth: 2, + animation: true, + animationSteps: 100, + animationEasing: "easeOutBounce", + animateRotate: true, + animateScale: false, + onAnimationComplete: null + }; + + var config = (options)? mergeChartConfig(chart.Pie.defaults,options) : chart.Pie.defaults; + + return new Pie(data,config,context); + }; + +// ///////////////////////////////////////////////////////////////// +// Doughnut helper +// ///////////////////////////////////////////////////////////////// + + this.Doughnut = function(data,options) { + + chart.Doughnut.defaults = { + segmentShowStroke: true, + segmentStrokeColor: "#fff", + segmentStrokeWidth: 2, + percentageInnerCutout: 50, + animation: true, + animationSteps: 100, + animationEasing: "easeOutBounce", + animateRotate: true, + animateScale: false, + onAnimationComplete: null + }; + + var config = (options)? mergeChartConfig(chart.Doughnut.defaults,options) : chart.Doughnut.defaults; + + return new Doughnut(data,config,context); + + }; + +// ///////////////////////////////////////////////////////////////// +// Line helper +// ///////////////////////////////////////////////////////////////// + + this.Line = function(data,options) { + + chart.Line.defaults = { + scaleOverlay: false, + scaleOverride: true, + scaleSteps: 20, + scaleStepWidth: 10, + scaleStartValue: 0, + scaleLineColor: "rgba(0,0,0,.1)", + scaleLineWidth: 1, + scaleShowLabels: true, + scaleLabel: "<%=value%>", + scaleFontFamily: "'Arial'", + scaleFontSize: 12, + scaleFontStyle: "normal", + scaleFontColor: "#666", + scaleShowGridLines: true, + scaleGridLineColor: "rgba(0,0,0,.05)", + scaleGridLineWidth: 1, + bezierCurve: true, + pointDot: true, + pointDotRadius: 4, + pointDotStrokeWidth: 2, + datasetStroke: true, + datasetStrokeWidth: 2, + datasetFill: true, + animation: true, + animationSteps: 60, + animationEasing: "easeOutQuart", + onAnimationComplete: null + }; + + var config = (options) ? mergeChartConfig(chart.Line.defaults,options) : chart.Line.defaults; + + return new Line(data,config,context); + } + +// ///////////////////////////////////////////////////////////////// +// Bar helper +// ///////////////////////////////////////////////////////////////// + + this.Bar = function(data,options) { + + chart.Bar.defaults = { + scaleOverlay: false, + scaleOverride: false, + scaleSteps: null, + scaleStepWidth: null, + scaleStartValue: null, + scaleLineColor: "rgba(0,0,0,.1)", + scaleLineWidth: 1, + scaleShowLabels: true, + scaleLabel: "<%=value%>", + scaleFontFamily: "'Arial'", + scaleFontSize: 12, + scaleFontStyle: "normal", + scaleFontColor: "#666", + scaleShowGridLines: true, + scaleGridLineColor: "rgba(0,0,0,.05)", + scaleGridLineWidth: 1, + barShowStroke: true, + barStrokeWidth: 2, + barValueSpacing: 5, + barDatasetSpacing: 1, + animation: true, + animationSteps: 60, + animationEasing: "easeOutQuart", + onAnimationComplete: null + }; + + var config = (options) ? mergeChartConfig(chart.Bar.defaults,options) : chart.Bar.defaults; + + return new Bar(data,config,context); + } + +// ///////////////////////////////////////////////////////////////// +// Polar Area implementation +// ///////////////////////////////////////////////////////////////// + + var PolarArea = function(data,config,ctx) { + + var maxSize; + var scaleHop; + var calculatedScale; + var labelHeight; + var scaleHeight; + var valueBounds; + var labelTemplateString; + + // ///////////////////////////////////////////////////////////////// + // initialisation + // ///////////////////////////////////////////////////////////////// + + this.init = function() { + + calculateDrawingSizes(); + + valueBounds = getValueBounds(); + + labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null; + + if (!config.scaleOverride) { + calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); + } else { + calculatedScale = { + steps: config.scaleSteps, + stepValue: config.scaleStepWidth, + graphMin: config.scaleStartValue, + labels: [] + } + populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth); + } + + scaleHop = maxSize/(calculatedScale.steps); + } + + // ///////////////////////////////////////////////////////////////// + // drawing + // ///////////////////////////////////////////////////////////////// + + this.draw = function(progress) { + + clear(ctx); + + if(config.scaleOverlay) { + drawAllSegments(progress); + drawScale(); + } else { + drawScale(); + drawAllSegments(progress); + } + } + + // /////////////////////////////////////////////////////////////// + + function calculateDrawingSizes() { + + maxSize = (Min([width,height])/2); + maxSize -= Max([config.scaleFontSize*0.5,config.scaleLineWidth*0.5]); + + labelHeight = config.scaleFontSize*2; + + if (config.scaleShowLabelBackdrop) { + + labelHeight += (2 * config.scaleBackdropPaddingY); + maxSize -= config.scaleBackdropPaddingY*1.5; + } + + scaleHeight = maxSize; + labelHeight = Default(labelHeight,5); + } + + function drawScale() { + + for (var i=0; i upperValue) {upperValue = data[i].value;} + if (data[i].value < lowerValue) {lowerValue = data[i].value;} + }; + + var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); + var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); + + return { + maxValue: upperValue, + minValue: lowerValue, + maxSteps: maxSteps, + minSteps: minSteps + }; + } + } + +// ///////////////////////////////////////////////////////////////// +// Radar implementation +// ///////////////////////////////////////////////////////////////// + + var Radar = function (data, config, ctx) { + + var maxSize; + var scaleHop; + var calculatedScale; + var labelHeight; + var scaleHeight; + var valueBounds; + var labelTemplateString; + + // ///////////////////////////////////////////////////////////////// + // initialisation + // ///////////////////////////////////////////////////////////////// + + this.init = function () { + + if (!data.labels) data.labels = []; + + calculateDrawingSizes(); + + var valueBounds = getValueBounds(); + + labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null; + + if (!config.scaleOverride) { + calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); + } else { + calculatedScale = { + steps: config.scaleSteps, + stepValue: config.scaleStepWidth, + graphMin: config.scaleStartValue, + labels: [] + } + populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth); + } + + scaleHop = maxSize/(calculatedScale.steps); + } + + // ///////////////////////////////////////////////////////////////// + // drawing + // ///////////////////////////////////////////////////////////////// + + this.draw = function(progress) { + + clear(ctx); + + if(config.scaleOverlay) { + drawAllDataPoints(progress); + drawScale(); + } else { + drawScale(); + drawAllDataPoints(progress); + } + } + + // /////////////////////////////////////////////////////////////// + + function drawAllDataPoints(animationDecimal) { + + var rotationDegree = (2*Math.PI)/data.datasets[0].data.length; + + ctx.save(); + ctx.translate(width/2,height/2); + + for (var i=0; i Math.PI) { + ctx.textAlign = "right"; + } else { + ctx.textAlign = "left"; + } + ctx.textBaseline = "middle"; + ctx.fillText(data.labels[k],opposite,-adjacent); + } + ctx.restore(); + }; + + function calculateDrawingSizes() { + + maxSize = (Min([width,height])/2); + labelHeight = config.scaleFontSize*2; + + var labelLength = 0; + + for (var i=0; ilabelLength) labelLength = textMeasurement; + } + + maxSize -= Max([labelLength,((config.pointLabelFontSize/2)*1.5)]); + maxSize -= config.pointLabelFontSize; + maxSize = CapValue(maxSize, null, 0); + + scaleHeight = maxSize; + labelHeight = Default(labelHeight,5); + }; + + function getValueBounds() { + + var upperValue = Number.MIN_VALUE; + var lowerValue = Number.MAX_VALUE; + + for (var i=0; i upperValue) {upperValue = data.datasets[i].data[j]} + if (data.datasets[i].data[j] < lowerValue) {lowerValue = data.datasets[i].data[j]} + } + } + + var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); + var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); + + return { + maxValue: upperValue, + minValue: lowerValue, + maxSteps: maxSteps, + minSteps: minSteps + }; + } + } + +// ///////////////////////////////////////////////////////////////// +// Pie implementation +// ///////////////////////////////////////////////////////////////// + + var Pie = function(data,config,ctx) { + + var segmentTotal = 0; + var pieRadius = Min([height/2,width/2]) - 5; + + // ///////////////////////////////////////////////////////////////// + // initialisation + // ///////////////////////////////////////////////////////////////// + + this.init = function () { + + for (var i=0; i 0) { + ctx.save(); + ctx.textAlign = "right"; + } else{ + ctx.textAlign = "center"; + } + ctx.fillStyle = config.scaleFontColor; + + for (var i=0; i 0) { + ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize); + ctx.rotate(-(rotateLabels * (Math.PI/180))); + ctx.fillText(data.labels[i], 0,0); + ctx.restore(); + } else { + ctx.fillText(data.labels[i], yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize+3); + } + + ctx.beginPath(); + ctx.moveTo(yAxisPosX + i * valueHop, xAxisPosY+3); + + if(config.scaleShowGridLines && i>0) { + ctx.lineWidth = config.scaleGridLineWidth; + ctx.strokeStyle = config.scaleGridLineColor; + ctx.lineTo(yAxisPosX + i * valueHop, 5); + } else{ + ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY+3); + } + ctx.stroke(); + } + + ctx.lineWidth = config.scaleLineWidth; + ctx.strokeStyle = config.scaleLineColor; + ctx.beginPath(); + ctx.moveTo(yAxisPosX,xAxisPosY+5); + ctx.lineTo(yAxisPosX,5); + ctx.stroke(); + ctx.textAlign = "right"; + ctx.textBaseline = "middle"; + + for (var j=0; j longestText)? measuredText : longestText; + } + longestText +=10; + } + + xAxisLength = width - longestText - widestXLabel; + valueHop = Math.floor(xAxisLength/(data.labels.length-1)); + + yAxisPosX = width-widestXLabel/2-xAxisLength; + xAxisPosY = scaleHeight + config.scaleFontSize/2; + } + + function calculateDrawingSizes() { + + maxSize = height; + + ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily; + + widestXLabel = 1; + + for (var i=0; i widestXLabel)? textLength : widestXLabel; + } + + if (width/data.labels.length < widestXLabel) { + + rotateLabels = 45; + + if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel) { + rotateLabels = 90; + maxSize -= widestXLabel; + } else{ + maxSize -= Math.sin(rotateLabels) * widestXLabel; + } + } else{ + maxSize -= config.scaleFontSize; + } + + maxSize -= 5; + + labelHeight = config.scaleFontSize; + + maxSize -= labelHeight; + + scaleHeight = maxSize; + } + + function getValueBounds() { + + var upperValue = Number.MIN_VALUE; + var lowerValue = Number.MAX_VALUE; + + for (var i=0; i upperValue) { upperValue = data.datasets[i].data[j] }; + if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] }; + } + }; + + var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); + var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); + + return { + maxValue: upperValue, + minValue: lowerValue, + maxSteps: maxSteps, + minSteps: minSteps + }; + } + } + +// ///////////////////////////////////////////////////////////////// +// Bar implementation +// ///////////////////////////////////////////////////////////////// + + var Bar = function(data, config, ctx) { + + var maxSize; + var scaleHop; + var calculatedScale; + var labelHeight; + var scaleHeight; + var valueBounds; + var labelTemplateString; + var valueHop; + var widestXLabel; + var xAxisLength; + var yAxisPosX; + var xAxisPosY; + var barWidth; + var rotateLabels = 0; + + // ///////////////////////////////////////////////////////////////// + // initialisation + // ///////////////////////////////////////////////////////////////// + + this.init = function () { + + calculateDrawingSizes(); + + valueBounds = getValueBounds(); + + labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : ""; + + if (!config.scaleOverride) { + calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); + } else { + calculatedScale = { + steps: config.scaleSteps, + stepValue: config.scaleStepWidth, + graphMin: config.scaleStartValue, + labels: [] + } + populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth); + } + + scaleHop = Math.floor(scaleHeight/calculatedScale.steps); + calculateXAxisSize(); + } + + // ///////////////////////////////////////////////////////////////// + // drawing + // ///////////////////////////////////////////////////////////////// + + this.draw = function (progress) { + + clear(ctx); + + if(config.scaleOverlay) { + drawBars(progress); + drawScale(); + } else { + drawScale(); + drawBars(progress); + } + } + + // /////////////////////////////////////////////////////////////// + + function drawBars(animPc) { + + ctx.lineWidth = config.barStrokeWidth; + + for (var i=0; i 0) { + ctx.save(); + ctx.textAlign = "right"; + } else{ + ctx.textAlign = "center"; + } + + ctx.fillStyle = config.scaleFontColor; + + for (var i=0; i 0) { + ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize); + ctx.rotate(-(rotateLabels * (Math.PI/180))); + ctx.fillText(data.labels[i], 0,0); + ctx.restore(); + } else { + ctx.fillText(data.labels[i], yAxisPosX + i*valueHop + valueHop/2,xAxisPosY + config.scaleFontSize+3); + } + + ctx.beginPath(); + ctx.moveTo(yAxisPosX + (i+1) * valueHop, xAxisPosY+3); + ctx.lineWidth = config.scaleGridLineWidth; + ctx.strokeStyle = config.scaleGridLineColor; + ctx.lineTo(yAxisPosX + (i+1) * valueHop, 5); + ctx.stroke(); + } + + ctx.lineWidth = config.scaleLineWidth; + ctx.strokeStyle = config.scaleLineColor; + ctx.beginPath(); + ctx.moveTo(yAxisPosX,xAxisPosY+5); + ctx.lineTo(yAxisPosX,5); + ctx.stroke(); + ctx.textAlign = "right"; + ctx.textBaseline = "middle"; + + for (var j=0; j longestText)? measuredText : longestText; + } + + longestText +=10; + } + + xAxisLength = width - longestText - widestXLabel; + valueHop = Math.floor(xAxisLength/(data.labels.length)); + + barWidth = (valueHop - config.scaleGridLineWidth*2 - (config.barValueSpacing*2) - (config.barDatasetSpacing*data.datasets.length-1) - ((config.barStrokeWidth/2)*data.datasets.length-1))/data.datasets.length; + + yAxisPosX = width-widestXLabel/2-xAxisLength; + xAxisPosY = scaleHeight + config.scaleFontSize/2; + } + + function calculateDrawingSizes() { + + maxSize = height; + ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily; + widestXLabel = 1; + + for (var i=0; i widestXLabel)? textLength : widestXLabel; + } + + if (width/data.labels.length < widestXLabel) { + rotateLabels = 45; + if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel) { + rotateLabels = 90; + maxSize -= widestXLabel; + } else{ + maxSize -= Math.sin(rotateLabels) * widestXLabel; + } + } else{ + maxSize -= config.scaleFontSize; + } + + maxSize -= 5; + + labelHeight = config.scaleFontSize; + + maxSize -= labelHeight; + + scaleHeight = maxSize; + } + + function getValueBounds() { + + var upperValue = Number.MIN_VALUE; + var lowerValue = Number.MAX_VALUE; + + for (var i=0; i upperValue) { upperValue = data.datasets[i].data[j] }; + if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] }; + } + }; + + var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); + var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); + + return { + maxValue: upperValue, + minValue: lowerValue, + maxSteps: maxSteps, + minSteps: minSteps + }; + } + } + +// ///////////////////////////////////////////////////////////////// +// Helper functions +// ///////////////////////////////////////////////////////////////// + + var clear = function(c) { + c.clearRect(0, 0, width, height); + }; + + + function calculateOffset(val,calculatedScale,scaleHop) { + + var outerValue = calculatedScale.steps * calculatedScale.stepValue; + var adjustedValue = val - calculatedScale.graphMin; + var scalingFactor = CapValue(adjustedValue/outerValue,1,0); + + return (scaleHop*calculatedScale.steps) * scalingFactor; + } + + function calculateScale(drawingHeight,maxSteps,minSteps,maxValue,minValue,labelTemplateString) { + + var graphMin,graphMax,graphRange,stepValue,numberOfSteps,valueRange,rangeOrderOfMagnitude,decimalNum; + + valueRange = maxValue - minValue; + rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange); + graphMin = Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude); + graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude); + graphRange = graphMax - graphMin; + stepValue = Math.pow(10, rangeOrderOfMagnitude); + numberOfSteps = Math.round(graphRange / stepValue); + + while(numberOfSteps < minSteps || numberOfSteps > maxSteps) { + if (numberOfSteps < minSteps) { + stepValue /= 2; + numberOfSteps = Math.round(graphRange/stepValue); + } else{ + stepValue *=2; + numberOfSteps = Math.round(graphRange/stepValue); + } + }; + + var labels = []; + + populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue); + + return { + steps: numberOfSteps, + stepValue: stepValue, + graphMin: graphMin, + labels: labels + } + + function calculateOrderOfMagnitude(val) { + return Math.floor(Math.log(val) / Math.LN10); + } + } + + function populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue) { + if (labelTemplateString) { + for (var i = 1; i < numberOfSteps + 1; i++) { + labels.push(tmpl(labelTemplateString, {value: (graphMin + (stepValue * i)).toFixed(getDecimalPlaces(stepValue))})); + } + } + } + + function Max(array) { + return Math.max.apply(Math, array); + }; + + function Min(array) { + return Math.min.apply(Math, array); + }; + + function Default(userDeclared,valueIfFalse) { + if(!userDeclared) { + return valueIfFalse; + } else { + return userDeclared; + } + }; + + function isNumber(n) { + return !isNaN(parseFloat(n)) && isFinite(n); + } + + function CapValue(valueToCap, maxValue, minValue) { + if(isNumber(maxValue)) { + if( valueToCap > maxValue ) { + return maxValue; + } + } + if(isNumber(minValue)) { + if ( valueToCap < minValue ) { + return minValue; + } + } + return valueToCap; + } + + function getDecimalPlaces (num) { + var numberOfDecimalPlaces; + if (num%1!=0) { + return num.toString().split(".")[1].length + } else { + return 0; + } + } + + function mergeChartConfig(defaults,userDefined) { + var returnObj = {}; + for (var attrname in defaults) { returnObj[attrname] = defaults[attrname]; } + for (var attrname in userDefined) { returnObj[attrname] = userDefined[attrname]; } + return returnObj; + } + + var cache = {}; + + function tmpl(str, data) { + var fn = !/\W/.test(str) ? + cache[str] = cache[str] || + tmpl(document.getElementById(str).innerHTML) : + + new Function("obj", + "var p=[],print=function() {p.push.apply(p,arguments);};" + + "with(obj) {p.push('" + + str + .replace(/[\r\t\n]/g, " ") + .split("<%").join("\t") + .replace(/((^|%>)[^\t]*)'/g, "$1\r") + .replace(/\t=(.*?)%>/g, "',$1,'") + .split("\t").join("');") + .split("%>").join("p.push('") + .split("\r").join("\\'") + + "');}return p.join('');"); + + return data ? fn( data ) : fn; + }; +} + +// ///////////////////////////////////////////////////////////////// +// Credits +// ///////////////////////////////////////////////////////////////// + +/*! + * Chart.js + * http://chartjs.org/ + * + * Copyright 2013 Nick Downie + * Released under the MIT license + * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md + */ + +// Copyright (c) 2013 Nick Downie + +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, copy, +// modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. diff --git a/qml/jbQuick/Charts/QChart.qml b/qml/jbQuick/Charts/QChart.qml new file mode 100644 index 0000000..6824b97 --- /dev/null +++ b/qml/jbQuick/Charts/QChart.qml @@ -0,0 +1,114 @@ +/* QChart.qml --- + * + * Author: Julien Wintz + * Created: Thu Feb 13 20:59:40 2014 (+0100) + * Version: + * Last-Updated: jeu. mars 6 12:55:14 2014 (+0100) + * By: Julien Wintz + * Update #: 69 + */ + +/* Change Log: + * + */ + +import QtQuick 2.0 + +import "QChart.js" as Charts + +Canvas { + + id: canvas; + +// /////////////////////////////////////////////////////////////// + + property var chart; + property var chartData; + property int chartType: 0; + property bool chartAnimated: true; + property alias chartAnimationEasing: chartAnimator.easing.type; + property alias chartAnimationDuration: chartAnimator.duration; + property int chartAnimationProgress: 0; + property var chartOptions: ({}) + +// ///////////////////////////////////////////////////////////////// +// Callbacks +// ///////////////////////////////////////////////////////////////// + + onPaint: { + var ctx = canvas.getContext("2d"); + /* Reset the canvas context to allow resize events to properly redraw + the surface with an updated window size */ + ctx.reset() + + switch(chartType) { + case Charts.ChartType.BAR: + chart = new Charts.Chart(canvas, ctx).Bar(chartData, chartOptions); + break; + case Charts.ChartType.DOUGHNUT: + chart = new Charts.Chart(canvas, ctx).Doughnut(chartData, chartOptions); + break; + case Charts.ChartType.LINE: + chart = new Charts.Chart(canvas, ctx).Line(chartData, chartOptions); + break; + case Charts.ChartType.PIE: + chart = new Charts.Chart(canvas, ctx).Pie(chartData, chartOptions); + break; + case Charts.ChartType.POLAR: + chart = new Charts.Chart(canvas, ctx).PolarArea(chartData, chartOptions); + break; + case Charts.ChartType.RADAR: + chart = new Charts.Chart(canvas, ctx).Radar(chartData, chartOptions); + break; + default: + console.log('Chart type should be specified.'); + } + + chart.init(); + + if (chartAnimated) + chartAnimator.start(); + else + chartAnimationProgress = 100; + + chart.draw(chartAnimationProgress/100); + } + + onHeightChanged: { + requestPaint(); + } + + onWidthChanged: { + requestPaint(); + } + + onChartAnimationProgressChanged: { + requestPaint(); + } + + onChartDataChanged: { + requestPaint(); + } + +// ///////////////////////////////////////////////////////////////// +// Functions +// ///////////////////////////////////////////////////////////////// + + function repaint() { + chartAnimationProgress = 0; + chartAnimator.start(); + } + +// ///////////////////////////////////////////////////////////////// +// Internals +// ///////////////////////////////////////////////////////////////// + + PropertyAnimation { + id: chartAnimator; + target: canvas; + property: "chartAnimationProgress"; + to: 100; + duration: 500; + easing.type: Easing.InOutElastic; + } +} diff --git a/qml/jbQuick/Charts/qmldir b/qml/jbQuick/Charts/qmldir new file mode 100644 index 0000000..7a0f914 --- /dev/null +++ b/qml/jbQuick/Charts/qmldir @@ -0,0 +1,18 @@ +### qmldir --- +## +## Author: Julien Wintz +## Created: Thu Feb 13 14:36:00 2014 (+0100) +## Version: +## Last-Updated: +## By: +## Update #: 45 +###################################################################### +## +### Change Log: +## +###################################################################### + +module jbQuick.Charts + +Chart 1.0 QChart.qml +Charts 1.0 QChart.js diff --git a/qml/main.qml b/qml/main.qml new file mode 100644 index 0000000..357b9c1 --- /dev/null +++ b/qml/main.qml @@ -0,0 +1,88 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.0 +import QtQuick.Layouts 1.0 +import "firmata" + +ApplicationWindow{ + id: main + visible: true + width: 640 + height: 480 + title: qsTr("Hello World") + PortSelector { + id: port + anchors.top: main.top + height:40 + width: main.width + Rectangle { + visible: false + anchors.fill: parent + color: "black" + opacity: 0.5 + } + + Component.onCompleted: { + + } + onDataReceived: { + dataUpdate(rawValue) + } + + + + } + QChartGallery{ + id: gallery + anchors.left: main.left + anchors.top: port.bottom + width: main.width + height: main.height - port.height + + Rectangle{ + visible: false + anchors.fill: parent + color: "green" + opacity: 0.5 + } + } + Timer { + id: timer + interval: 100 + running: false + repeat: true + onTriggered: { + var d = new Date() + gallery.chart_line_data.ChartLineData["labels"].shift() + gallery.chart_line_data.ChartLineData["labels"].push(d.getSeconds() + "." + d.getMilliseconds() ) + gallery.chart_line_data.ChartLineData["datasets"][0]["data"].shift() + gallery.chart_line_data.ChartLineData["datasets"][0]["data"].push(Math.floor(Math.random() * 400 +1 ).toString()) + gallery.chart_line_data.ChartLineData["datasets"][1]["data"].shift() + gallery.chart_line_data.ChartLineData["datasets"][1]["data"].push(Math.floor(Math.random() * 400 +1 ).toString()) + gallery.chart_line.repaint() + + } + } + function dataUpdate(rawValue){ + var d = new Date() + gallery.chart_line_data.ChartLineData["labels"].shift() + gallery.chart_line_data.ChartLineData["labels"].push(d.getSeconds() + "." + d.getMilliseconds() ) + gallery.chart_line_data.ChartLineData["datasets"][0]["data"].shift() + gallery.chart_line_data.ChartLineData["datasets"][0]["data"].push(rawValue.toString()) +// gallery.chart_line_data.ChartLineData["datasets"][1]["data"].shift() +// gallery.chart_line_data.ChartLineData["datasets"][1]["data"].push(Math.floor(Math.random() * 400 +1 ).toString()) + gallery.chart_line.repaint() + + } +} + + +// footer: TabBar { +// id: tabBar +// currentIndex: swipeView.currentIndex +// TabButton { +// text: qsTr("First") +// } +// TabButton { +// text: qsTr("Second") +// } +// } diff --git a/ultra_sonic.pro b/ultra_sonic.pro new file mode 100644 index 0000000..2992c6c --- /dev/null +++ b/ultra_sonic.pro @@ -0,0 +1,13 @@ +QT += qml quick + +CONFIG += c++11 + +SOURCES += main.cpp + +RESOURCES += qml.qrc + +# Additional import path used to resolve QML modules in Qt Creator's code model +QML_IMPORT_PATH = qml + +# Default rules for deployment. +include(deployment.pri)