UPDATE 1 - i can get it to work using Javascript - but that seems to be a little bit unoptimized (from 2-3% to 30% load with qmlscene.exe when activated) - complete recreation when the model changes (its not ever growing), is that the only way or is there a more "declarative" style available?
How can i add a PathLine to a ShapePath based on a Model?
i know how to use a Repeater from outside of Items but not how to implode them inside of Items
do i need some sort of Repeater-Delegate on pathElements?
and i don't want to use the LineSeries component
import QtQuick.Shapes 1.15
import QtQuick 2.15
Shape {
ListModel {
id: myPositions
ListElement { x: 0; y:38 }
ListElement { x: 10; y: 28 }
ListElement { x: 20; y: 30 }
ListElement { x: 30; y: 14 }
}
ShapePath {
strokeColor: "black"
strokeWidth: 1
fillColor: "transparent"
startX: 0
startY: 0
PathLine { x: 0; y: 38 }
PathLine { x: 10; y: 28 }
PathLine { x: 20; y: 30 }
PathLine { x: 30; y: 14 }
// Repeater {
// model: myPositions
// PathLine { x: model.x; y: model.y }
// }
}
}
UPDATE 1
import QtQuick.Shapes 1.15
import QtQuick 2.15
Shape {
ListModel {
id: myPositions
}
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
Timer
{
interval: 100
running: true
repeat: true
property real myX: 0
onTriggered: {
myPositions.append({"x":myX, "y":getRandomInt(0,30)})
myX = myX + 10
}
}
function createPathLineElements(positionsModel, shapePath)
{
var pathElements = []
for (var i = 0; i < positionsModel.count; i++)
{
var pos = myPositions.get(i)
var pathLine = Qt.createQmlObject('import QtQuick 2.15; PathLine {}',
shapePath);
pathLine.x = pos.x
pathLine.y = pos.y
pathElements.push(pathLine)
}
return pathElements
}
ShapePath {
id: myPath
strokeColor: "black"
strokeWidth: 1
fillColor: "transparent"
startX: 0
startY: 0
pathElements: createPathLineElements(myPositions, myPath)
}
}
Use an Instantiator:
Shape {
ListModel {
id: myPositions
}
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
Timer {
interval: 100
running: true
repeat: true
property real myX: 0
onTriggered: {
myPositions.append({"x":myX, "y":getRandomInt(0,30)})
myX = myX + 10
}
}
ShapePath {
id: myPath
fillColor: "transparent"
strokeColor: "red"
capStyle: ShapePath.RoundCap
joinStyle: ShapePath.RoundJoin
strokeWidth: 3
strokeStyle: ShapePath.SolidLine
}
Instantiator {
model: myPositions
onObjectAdded: myPath.pathElements.push(object)
PathLine {
x: model.x
y: model.y
}
}
}
You can't use repeater in that element.
The most performant way is to use QQuickItem in order to create a custom Item which draws incremental path.
Yet another simpler ways are:
1- Use PathSvg element and set its path runtime like below:
ShapePath {
fillColor: "transparent"
strokeColor: "red"
capStyle: ShapePath.RoundCap
joinStyle: ShapePath.RoundJoin
strokeWidth: 3
strokeStyle: ShapePath.SolidLine
PathSvg { id: ps; path: parent.p } //<== fill path property using js
property string p: ""
Component.onCompleted: {
for ( var i = 0; i < myModel.count; i++) {
p += "L %1 %2".arg(myModel.get(i).x).arg(myModel.get(i).y);
}
ps.path = p;
}
}
2- If you know steps, so you can pre-declare all PathLines and then set their values runtime. Just like a heart rate line on a health care monitor.
Related
I need scale and rotate a Rectangle around mouse point. When the Rectangle is not rotated my solution works fine, but if I apply Rotation transform I face the problem - my Rectangle move to an unexpected point. In my solution I use a MouseArea for drag the Rectangle, Scale and Rotation transforms for scale and rotate.
import QtQuick 2.0
Rectangle {
id: root
color: "gray"
Rectangle {
color: "black"
width: 360
height: 200
opacity: 0.5
x: 500
y: 350
}
Rectangle {
id: sample
color: "green"
width: 360
height: 200
opacity: 0.5
x: 500
y: 350
property real currX: 0
property real currY: 0
property real currZoom: 1
property real maxZoom: 5
property real minZoom: 0.5
transform: [
Scale {
id: scaler
origin.x: sample.currX
origin.y: sample.currY
xScale: sample.currZoom
yScale: sample.currZoom
},
Rotation{
id: rotation
origin.x: 180
origin.y: 100
angle: 30
}
]
MouseArea{
id: mouseArea
anchors.fill: parent
drag.target: sample
onClicked: mouse => {
zoom(true, mouse.x, mouse.y)
}
onWheel: (wheel) => {
var isIn = wheel.angleDelta.y > 0
zoom(isIn, wheel.x, wheel.y)
}
function zoom(isIn, x, y) {
var prevZoom = sample.currZoom
var prevX = sample.currX
var prevY = sample.currY
sample.currX = x
sample.currY = y
sample.currZoom = calculateZoom(isIn, prevZoom)
sample.x = sample.x + (prevX - sample.currX) * (1 - prevZoom)
sample.y = sample.y + (prevY - sample.currY) * (1 - prevZoom)
printSamplePostion()
}
function calculateZoom(isIn, prevZoom) {
var newZoom = isIn ? prevZoom + 0.1 : prevZoom - 0.1
if (newZoom > mouseArea.maxZoom)
newZoom = mouseArea.maxZoom
if (newZoom < mouseArea.minZoom)
newZoom = mouseArea.minZoom
return newZoom
}
function printSamplePostion() {
console.log("== x: 500 y: 350 ======")
console.log("-- x: " + sample.x)
console.log("-- y: " + sample.y)
console.log("=======================")
}
}
}
}
Thanks in advance
I have a qml Line Chart with DateTime X Axis and I need to measure time difference(dx) between two points similar to this chart
Here is the Chart code with sample data
ChartView {
id:chart
title: "Line"
anchors.fill: parent
antialiasing: true
DateTimeAxis {
id: axisX
format: "hh:mm:ss,ms"
tickCount: 10
}
ValueAxis {
id: axisY
min: 0
max: 10
}
LineSeries {
id:series1
name: "LineSeries"
axisX: axisX
axisY: axisY
pointsVisible: true
XYPoint { x: Date.parse("2020-10-09 05:51:00:500"); y: 0 }
XYPoint { x: Date.parse("2020-10-09 05:51:00:600"); y: 2 }
XYPoint { x: Date.parse("2020-10-09 05:52:00:100"); y: 4 }
XYPoint { x: Date.parse("2020-10-09 05:53:00:20"); y: 5 }
XYPoint { x: Date.parse("2020-10-09 05:54:00:200"); y: 8 }
XYPoint { x: Date.parse("2020-10-09 05:55:00:100"); y: 7 }
XYPoint { x: Date.parse("2020-10-09 05:56:00:200"); y: 6 }
XYPoint { x: Date.parse("2020-10-09 05:57:00:400"); y: 2 }
}
}
Any idea how to achieve this using QML or C++?
I roughly did like this. You should adjust time difference handling better.
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
property int rectangeXPosition: 0
property int rectangleYPosition: 0
property int rWidth: 0
property int rHeight: 0
property int minuteDiff: 0
ChartView {
id:chart
title: "Line"
anchors.fill: parent
antialiasing: true
Rectangle
{
x: rectangeXPosition
y: rectangleYPosition
width: rWidth
height: rHeight
color: "red"
opacity: 0.2
Text {
anchors.left: parent.right
anchors.top: parent.top
text: minuteDiff + " minutes"
}
}
MouseArea
{
anchors.fill: parent
onPositionChanged:
{
rWidth = mouse.x - rectangeXPosition
rHeight = mouse.y - rectangleYPosition
var currentPoint = chart.mapToValue(Qt.point(mouse.x, mouse.y), series1)
var startPoint = chart.mapToValue(Qt.point(rectangeXPosition,rectangleYPosition),series1)
var dateTimeDiff = new Date(currentPoint.x - startPoint.x)
minuteDiff = dateTimeDiff.getMinutes()
}
onPressed:
{
var point = Qt.point(mouse.x, mouse.y);
rectangeXPosition = point.x
rectangleYPosition = point.y
}
onReleased:
{
rectangeXPosition = 0
rectangleYPosition = 0
rWidth = 0
rHeight = 0
}
}
DateTimeAxis {
id: axisX
format: "hh:mm:ss,ms"
tickCount: 10
}
ValueAxis {
id: axisY
min: 0
max: 10
}
LineSeries {
id:series1
name: "LineSeries"
axisX: axisX
axisY: axisY
pointsVisible: true
XYPoint { x: Date.parse("2020-10-09 05:51:00:500"); y: 0 }
XYPoint { x: Date.parse("2020-10-09 05:51:00:600"); y: 2 }
XYPoint { x: Date.parse("2020-10-09 05:52:00:100"); y: 4 }
XYPoint { x: Date.parse("2020-10-09 05:53:00:20"); y: 5 }
XYPoint { x: Date.parse("2020-10-09 05:54:00:200"); y: 8 }
XYPoint { x: Date.parse("2020-10-09 05:55:00:100"); y: 7 }
XYPoint { x: Date.parse("2020-10-09 05:56:00:200"); y: 6 }
XYPoint { x: Date.parse("2020-10-09 05:57:00:400"); y: 2 }
}
}
}
How do I place text at specific (x,y) locations within the plotting area of a QML ChartView type?
For example, I would like to place text at the location XYPoint{x: -3; Y: 20}
I don't want to place at window's(x,y), i want to put at plotting area's (x,y)
I read documentation,but i don't find any property !!!!!!
//ChartView for plotting points
ChartView{
id:chrt
anchors.fill: parent
height: parent.height
width: parent.width
legend.visible: false
backgroundColor: "black"
//X- axis
ValueAxis{
id: x_axis
min: -5
max: 0
tickCount: 6
}
//Right Y axis
ValueAxis{
id:right_y_axis
min:0
max:40
tickCount: 5
}
//Left Y axis
ValueAxis{
id:left_y_axis
min:0
max:40
tickCount: 5
}
//Line series for wave 1
LineSeries{
id:l1
axisY: left_y_axis
axisX:x_axis
color: "yellow"
width: 1
}
//Line series for wave 2
LineSeries{
id:l2
axisYRight: right_y_axis
style: Qt.DashLine
color: "yellow"
width: 0.6
}
}
Ok, you can use ChartView.mapToPosition to calculate the global position of the specified point from a series (or some another point inside the series), for example:
ChartView{
id:chart
anchors.fill: parent
backgroundColor: "black"
LineSeries{
id: series
XYPoint { x: 10; y: 5 }
XYPoint { x: 10; y: 1 }
XYPoint { x: 15; y: 5 }
XYPoint { x: 20; y: 10 }
XYPoint { x: 25; y: 5 }
XYPoint { x: 30; y: 20 }
XYPoint { x: 40; y: 10 }
}
Text {
id: txt
text: "Hello"
color: "white"
}
onWidthChanged: updatePointPosition();
onHeightChanged: updatePointPosition();
function updatePointPosition()
{
var p = chart.mapToPosition(series.at(3), series);
txt.x = p.x;
txt.y = p.y;
}
}
I have set a custom tooltip using the code
ChartView {
id: chart
anchors.fill: parent
antialiasing: true
ValueAxis {
id: axisY
tickCount: 3
}
DateTimeAxis{
id: xTime
gridVisible: false
}
ToolTip {
id: id_toolTip
contentItem: Text{
color: "#21be2b"
}
background: Rectangle {
border.color: "#21be2b"
}
}
SplineSeries{
id: chartseries
pointsVisible: true
pointLabelsVisible: false
useOpenGL: true
axisX: xTime
axisY: axisY
onClicked: {
id_toolTip.show("dd")
}
}
}
It gives an error when I click on the graph ..
TypeError: Property 'show' of object QQuickToolTip(0x37275d0) is not a function"
But below mentioned code will work.
chart.ToolTip.show("dd")
But I need custom tooltip
You can use the other properties as I show below:
ChartView {
id: chart
anchors.fill: parent
antialiasing: true
ValueAxis {
id: axisY
tickCount: 3
max: 4
min: 0
}
DateTimeAxis{
id: xTime
gridVisible: false
}
ToolTip {
id: id_tooltip
contentItem: Text{
color: "#21be2b"
text: id_tooltip.text
}
background: Rectangle {
border.color: "#21be2b"
}
}
SplineSeries{
id: chartseries
XYPoint { x: new Date('December 17, 1995 03:24:00').getTime() ; y: 0.0 }
XYPoint { x: new Date('December 18, 1995 04:25:00').getTime() ; y: 3.2 }
XYPoint { x: new Date('December 19, 1995 05:26:00').getTime() ; y: 2.4 }
XYPoint { x: new Date('December 20, 1995 06:27:00').getTime() ; y: 2.1 }
XYPoint { x: new Date('December 21, 1995 07:24:00').getTime() ; y: 0.0 }
XYPoint { x: new Date('December 22, 1995 08:25:00').getTime() ; y: 3.2 }
XYPoint { x: new Date('December 23, 1995 09:26:00').getTime() ; y: 2.4 }
XYPoint { x: new Date('December 24, 1995 10:27:00').getTime() ; y: 2.1 }
pointsVisible: true
pointLabelsVisible: false
useOpenGL: true
axisX: xTime
axisY: axisY
onClicked: {
var p = chart.mapToPosition(point)
var text = qsTr("x: %1, y: %2").arg(new Date(point.x).toLocaleDateString(Qt.locale("en_US"))).arg(point.y)
id_tooltip.x = p.x
id_tooltip.y = p.y - id_tooltip.height
id_tooltip.text = text
//id_tooltip.timeout = 1000
id_tooltip.visible = true
}
}
}
In the first case, id_toolTip.show("dd") , you try to call the show func of the instantiated version of QML::Tooltip whereas at the second case, chart.ToolTip.show("dd") you call the show function of the attached property Tooltip which exists for all the qml controls.
Acccording to the docs, if you don't provide a second parameter, then this means that show() is the func of the shared tooltip and not the one you instantiate. That might be the reason your code does not work.
I have a Canvas with the image, that I load via filedialog, how can I get the pixel array of this image?
I need convert it to grayscale by converting every pixel using formula and load it back to the Canvas.
Here the code:
import QtQuick 2.0
import QtQuick.Controls 2.1
import QtQuick.Controls.Material 2.1
import QtQuick.Layouts 1.2
import QtQuick.Dialogs 1.0
ApplicationWindow {
id: window
visible: true
width: 1000
height: 750
Material.theme: Material.Dark
Material.background: "#2C303A"
Material.accent: "#65B486"
Material.foreground: "#efefef"
GridLayout {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: 9
columns: 4
rows: 3
rowSpacing: 10
columnSpacing: 10
Canvas {
id: canvas
height: window.height - 15
Layout.columnSpan: 4
Layout.fillWidth: true
property bool loaded: false
property var filepath: ''
onDrawChanged: requestPaint()
onFilepathChanged: {
loadImage(filepath)
}
onImageLoaded: {
loaded = true
requestPaint()
}
onPaint: {
if (loaded) {
var ctx = getContext("2d");
ctx.drawImage(filepath, 0, 0, width, height)
}
if (to_grayscale) {
var ctx = getContext("2d");
var ar = ctx.getImageData(0, 0, width, height).data
for(var i in ar){
print(i)
}
}
}
}
FileDialog {
id: fileDialog
title: "Please choose a file"
nameFilters: ["Image files (*.jpg *.png *.jpeg)"]
onAccepted: {
console.log("You chose: " + fileDialog.fileUrls)
canvas.filepath = fileDialog.fileUrls
canvas.requestPaint()
}
onRejected: {
console.log("Canceled")
}
}
Drawer {
id: drawer
visible: true
modal: false
width: 0.33 * window.width
height: window.height
GridLayout {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: 9
columns: 2
rows: 3
rowSpacing: 10
columnSpacing: 10
Button {
text: 'Load image'
onClicked: fileDialog.visible = true
}
Button {
text: 'RGB to Grayscale'
onClicked: canvas.draw = true
}
}
}
}
}
I'm trying to get ImageData, but here's empty
I read that Canvas contain PixelArray, but I don't know how to get it.
Thank you.
To access the rgba values
var ar = ctx.getImageData(0, 0, width, height);
for( var x=0; x < ar.data.length; x=x+4 )
{
// To read RGBA values
var red = ar.data[x];
var green = ar.data[x + 1];
var blue = ar.data[x + 2];
var alpha = ar.data[x + 3];
console.log(red + ", " + green + ", " + blue + ", " + alpha );
// To convert to grey scale, modify rgba according to your formula
ar.data[x] = 0.2126 *ar.data[x] + 0.7152* ar.data[x+1] + 0.0722 *ar.data[x+2];
ar.data[x+1] = 0.2126 *ar.data[x] + 0.7152* ar.data[x+1] + 0.0722 *ar.data[x+2];
ar.data[x+2] = 0.2126 *ar.data[x] + 0.7152* ar.data[x+1] + 0.0722 *ar.data[x+2];
ar.data[x+3] = 255;
}
// update the canvas with new data
ctx.drawImage(ar.data, 0, 0);
You have to requestPaint() in onClicked slot of Button