I want to put a number of rectangles as overlays to show regions of interest on a ChartView of ScatterSeries. However when I try to do this it is clearly using a different coordinate system to that of the ScatterSeries as it is drawn in a completely different place.
For example the following is intended to draw a rectangle that captures all of the ScatterSeries but it just draws a small green rectangle top left as shown in the screenshot.
ChartView {
id: view
Layout.fillWidth : true
Layout.fillHeight : true
Rectangle {
id: rec
x: 30
y: 50
width: 40
height: 10
color: "green"
}
ScatterSeries{
id: series
XYPoint { x: 30; y: 50 }
XYPoint { x: 50; y: 60 }
XYPoint { x: 60; y: 50 }
XYPoint { x: 70; y: 60 }
axisX: ValueAxis {
min: 0
max: 100
}
axisY: ValueAxis {
min: 0
max: 100
}
}
The documentation suggests that the rectangle should use the coordinate system of the parent ChartView. I assume I actually want it to use the coordinate system of the ChartView scene. How do I do this?
To translate from the ScatterSeries coordinate system to pixel coordinates to place a child in ChartView use mapToPosition(...):
function updateRectangle() {
var topLeftPoint = view.mapToPosition(Qt.point(30,60), series)
var bottomRightPoint = view.mapToPosition(Qt.point(70,50), series)
rec.x = topLeftPoint.x
rec.y = topLeftPoint.y
rec.width = bottomRightPoint.x - topLeftPoint.x
rec.height = bottomRightPoint.y - topLeftPoint.y
}
Where series is your ScatterSeries and rec is your Rectangle.
Invoke the update function whenever the chart points get recalculated (e.g. after creation and size changes).
See also related question How to map QChart coordinate to QChartView point?
Related
Introduction
In my QtQuick (QML) project I need to use 3D model of dice (a cubic one) that should be rotated during animation process. I use View3D, because rotating dice will be inside of 3D-insertion which is inside my 2D project. I have downloaded a free-to-use dice model (.fbx) and successfully imported it in my project in form of ".mesh" file via Balsam Asset Import Tool. By choosing created mesh for a source of model and also applying the texture of dice via PrincipledMaterial, I perfectly see my dice model inside View3D.
The problem
When I tried to animate the rotation process of model, I noticed, that dice rotates incorrectly. I decided to look its axis via QtQuick3D.Helpers' AxisHelper and saw that starting points of axes X and Y located just fine but the Z axis' starting point is shifted from the center of the model by about 10 units.
What did I try
I tried to look up for useful properties of Model QML type (and Node because Model inherits Node type) that may allowed me to modify or specify the rotation axis of the object but I did not find anything. On the Stack Overflow there is one similar question but it does not helped me, because I should use View3D, not the whole Qt3D with Entity. Moreover, I tried to put translate property but it has only x and y fields and I did not seen any changes by modifying these.
What I want to know
I would like to know if there is a way to place rotation axis of 3D model inside the center of it. May be some Balsam Asset Import Tool's options can help me with that (I tried some options like dropNormals, removeComponentNormals, fbxPreservePivots but did not seen any changes)?
Also I am not good at 3D Modelling, so could you please tell me if .mesh or .fbx files of 3D models store the information of axis rotation or something like that.
One group of axes is a 3D scene axes, and other group is dice model axes
The code I have (View3DArea.qml - custom type)
import QtQuick
import QtQuick3D
import QtQuick3D.Helpers
Rectangle
{
id: root
width: parent.width/1.5
height: width
anchors.centerIn: parent
color: "lightgrey"
View3D
{
anchors.fill: parent
Node
{
PerspectiveCamera
{
id: _camera
z: 100
}
DirectionalLight
{
}
// Applying the texture
PrincipledMaterial
{
id: _priDiceMaterial
baseColorMap: Texture
{
source: "assets/assets/Dice_Diffuse.jpg.png"
}
}
// Model information
Model
{
id: _diceModel
source: "/assets/assets/dice_001.mesh"
materials: [_priDiceMaterial]
eulerRotation: Qt.vector3d(0, 0, 0)
position: Qt.vector3d(20, 20, 20)
AxisHelper {} // Visual axes of dice model
}
AxisHelper { id: _sceneAxis } // Visual axes of 3D scene
}
}
WasdController
{
controlledObject: _camera
}
}
[EDIT]
I reworked the answer by putting the Model inside the Node. You can change the axis of your Model by setting x, y or z properties. Then you apply rotation on the Node:
Node
{
id: node
Model
{
id: _diceModel
source: "/assets/assets/dice_001.mesh"
materials: [_priDiceMaterial]
x: 100
y: 100
z: 100
}
eulerRotation: Qt.vector3d(0, 45, 0)
}
Because dice is involved. I experimented with mixing #Cube and #Cylinder to create a dice effect. The approach uses the above strategy by offsetting the #Cylinder in different places. A pain point with this approach is, on certain faces, the #Cylinder needs to be rotated.
import QtQuick
import QtQuick.Controls
import QtQuick3D
Page {
background: Rectangle { color: "#848895" }
property color blk: Qt.rgba(0.2, 0.2, 0.2, 0.75)
property color wht: Qt.rgba(0.7, 0.7, 0.7, 0.75)
property color red: Qt.rgba(0.7, 0.2, 0.2, 0.75)
Node {
id: standAloneScene
DirectionalLight { ambientColor: Qt.rgba(1.0, 1.0, 1.0, 1.0) }
Node {
id: node
Model {
source: "#Cube"
materials: [
DefaultMaterial { diffuseColor: blk }
]
}
// 6
Dot { x: -25; y: 50; z: -25 }
Dot { x: 25; y: 50; z: -25 }
Dot { x: -25; y: 50 }
Dot { x: 25; y: 50 }
Dot { x: -25; y: 50; z: 25 }
Dot { x: 25; y: 50; z: 25 }
// 1
Dot { y: -50; c: red }
// 4
Dot { x: 50; y: -20; z: -25; r: "z"; c: red }
Dot { x: 50; y: 20; z: -25; r: "z"; c: red }
Dot { x: 50; y: -20; z: 25; r: "z"; c: red }
Dot { x: 50; y: 20; z: 25; r: "z"; c: red }
// 3
Dot { x: -50; y: -20; z: -25; r: "z" }
Dot { x: -50; r: "z" }
Dot { x: -50; y: 20; z: 25; r: "z" }
// 2
Dot { z: 50; x: 20; r: "x" }
Dot { z: 50; x: -20; r: "x" }
// 5
Dot { z: -50; x: -25; y: -25; r: "x" }
Dot { z: -50; x: 25; y: -25; r: "x" }
Dot { z: -50; r: "x" }
Dot { z: -50; x: -25; y: 25; r: "x" }
Dot { z: -50; x: 25; y: 25; r: "x" }
}
OrthographicCamera {
id: cameraOrthographicFront
lookAtNode: node
y: 800; z: 1000
}
}
View3D {
anchors.fill: parent
importScene: standAloneScene
camera: cameraOrthographicFront
}
NumberAnimation {
target: node
property: "eulerRotation.y"
loops: Animation.Infinite
running: true
from: 720; to: 0
duration: 10000
}
NumberAnimation {
target: node
property: "eulerRotation.x"
loops: Animation.Infinite
running: true
from: 360; to: 0
duration: 10000
}
}
// Dot.qml
import QtQuick
import QtQuick3D
Model {
property string r: ""
property color c: wht
source: "#Cylinder"
materials: [
DefaultMaterial { diffuseColor: c }
]
scale: Qt.vector3d(0.2, 0.05, 0.2)
eulerRotation.x: r === 'x' ? 90 : 0
eulerRotation.y: r === 'y' ? 90 : 0
eulerRotation.z: r === 'z' ? 90 : 0
}
You can Try it Online!
I have a QML OSM map and a MapQuickItem with Text source item:
MapQuickItem {
property alias rulerRotationAngle: rulerRotation.angle
id: rulerTextMapItem
visible: false
width: 2
height: 2
transform: Rotation {
id: rulerRotation
origin.x: rulerText.width/2;
origin.y: rulerText.height/2;
angle: 0
}
anchorPoint.x: rulerText.width/2
anchorPoint.y: rulerText.height/2
z:5
sourceItem: Text {
id: rulerText; horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: Material.color(Material.Amber, Material.Shade100)
text: "0.0 km";
}
}
I also have two points (QtPositioning.coordinate) and I want the text to rotate depending on the angle of the straight line (MapPolyLine) drawn between those points:
function drawRuler()
{
rulerLine.path = [];
rulerLine.addCoordinate(r_firstpoint);
rulerLine.addCoordinate(r_secondpoint);
rulerTextMapItem.visible = true;
rulerTextMapItem.coordinate = QtPositioning.coordinate((r_firstpoint.latitude+r_secondpoint.latitude)/2, (r_firstpoint.longitude+r_secondpoint.longitude)/2);
var atan = Math.atan2(r_secondpoint.longitude-r_firstpoint.longitude, r_secondpoint.latitude-r_firstpoint.latitude);
var angle = ((atan*180)/Math.PI); //used by another MapItem
var textAngle = angle+270;
if(textAngle>90 & textAngle<270) { textAngle+=180 }
if(angle>90 & angle<270) { angle +=180 }
rulerTextMapItem.rulerRotationAngle = textAngle;
}
However, text rotates correctly only at angles that are multiples of 90 degrees. At an angle of 45 degrees, the text deviates from the mappolyline by about 10-20 degrees.
I have no clue why it happens and appreciate any help.
Tried to move transform.origin of MapQuickItem - angle difference only gets bigger.
Tried to use Math.Atan instead of Math.Atan2 - no difference.
The main issue is this line and the order of inputs:
var atan = Math.atan2(
r_secondpoint.longitude-r_firstpoint.longitude,
r_secondpoint.latitude-r_firstpoint.latitude);
latitude should come before longitude, i.e.
var atan = Math.atan2(
r_secondpoint.latitude-r_firstpoint.latitude,
r_secondpoint.longitude-r_firstpoint.longitude);
Generally speaking, to use Math.atan2() to convert to an angle, you need to use one of the following patterns:
let radians = Math.atan2(vectorY, vectorX)
let degrees = Math.atan2(vectorY, vectorX) * 180 / Math.PI
Also over large angles, you definitely should use project your angular coordinates to a flat projection, e.g. QtPositioning.coordToMercator. (This point was raised in one of the earlier comments).
For very small angles you can get away with it because the earth can be approximated to a flat earth directly from angular coordinates, but, as the area goes, this fact quickly disappears.
The following code demonstrates Math.atan2() and how it must work with (vectorY, vectorX) inputs. It has two draggable squares and you watch that the text will always follow the direction of the blue line no matter where the squares are:
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Shapes
Page {
id: page
width: 200; height: 200
property int startX: rect1.x + rect1.width / 2
property int startY: rect1.y + rect1.height / 2
property int finishX: rect2.x + rect2.width / 2
property int finishY: rect2.y + rect2.height / 2
Rectangle {
id: rect1
x: 40; y: 40
width: 40; height: 40
color: "red"
Drag.active: dragArea.drag.active
Drag.hotSpot.x: 20
Drag.hotSpot.y: 20
MouseArea {
id: dragArea
anchors.fill: parent
drag.target: parent
}
}
Rectangle {
id: rect2
x: 400; y: 250
width: 40; height: 40
color: "red"
Drag.active: dragArea2.drag.active
Drag.hotSpot.x: 20
Drag.hotSpot.y: 20
MouseArea {
id: dragArea2
anchors.fill: parent
drag.target: parent
}
}
Shape {
id: shape
ShapePath {
strokeWidth: 4
strokeColor: "blue"
startX: page.startX
startY: page.startY
PathLine {
x: page.finishX
y: page.finishY
}
}
}
Item {
x: (startX + finishX) / 2
y: (startY + finishY) / 2
rotation: Math.atan2(finishY - startY, finishX - startX) * 180 / Math.PI
Frame {
anchors.centerIn: parent
background: Rectangle {
border.color: "black"
}
Text {
text: "Hello World"
}
}
}
}
You can Try it Online!
I'm very new to QML and I want to make a basic application that consists of a segmented circle with 20-30 segments (pizza slices) and a counter. The number on the counter is the number of segment being highlighted. I found a few ways to make segmented circles in other questions but unfortunately none of them seem to work for my assignment.
The only way I see making it right now is by redrwing all the segments every time the counter is changed, and changing the color of the needed segment. So is there an optimal way to implement this?
To reduce the complexity, let's work through a simplified version of the problem:
Assume there are 6 pieces
Assume we want to draw piece 2
Assume we want to fit it in a 300x300 rectangle
Here's the math:
Each piece will occupy 60 degrees (i.e. 360 / 6)
Piece 2 will occupy angles from 120 to 180
To render the piece the drawing will be:
From the center point (150, 150)
Then (150 + 150 * cos(120), 150 + 150 * sin(120))
Then (150 + 150 * cos(180), 150 + 150 * sin(180))
Then back to the center point (150, 150)
Instead of a straight line, we want to draw a curve line between points 2 and points 3.
To render this, we can use Shape, ShapePath, PathLine, and PathArc.
To generalize, we can replace 6 with 20 and generalize all formulas accordingly. To draw 20 piece slices, we can make use of a Repeater, e.g.
Repeater {
model: 20
PizzaPiece {
piece: index
}
}
To polish it off, I added a Slider so you can interactively change the number of pieces you want from 0-20 and set the color to "orange", otherwise it will be a light yellow "#ffe".
Repeater {
model: 20
PizzaPiece {
piece: index
fillColor: index < slider.value ? "orange" : "#ffe"
}
}
Slider {
id: slider
from: 0
to: 20
stepSize: 1
}
As an extra bonus, I added a TapHandler so that each piece is clickable. If you leave the mouse pressed down, the piece will appear "red" until you release the mouse.
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
id: page
property int pieces: 20
Rectangle {
anchors.centerIn: parent
width: 300
height: 300
border.color: "grey"
Repeater {
model: pieces
PizzaPiece {
anchors.fill: parent
anchors.margins: 10
pieces: page.pieces
piece: index
fillColor: pressed ? "red" : index < slider.value ? "orange" : "#ffe"
onClicked: {
slider.value = index + 1;
}
}
}
}
footer: Frame {
RowLayout {
width: parent.width
Label {
text: slider.value
}
Slider {
id: slider
Layout.fillWidth: true
from: 0
to: pieces
value: 3
stepSize: 1
}
}
}
}
//PizzaPiece.qml
import QtQuick
import QtQuick.Shapes
Shape {
id: pizzaPiece
property int pieces: 20
property int piece: 0
property real from: piece * (360 / pieces)
property real to: (piece + 1) * (360 / pieces)
property real centerX: width / 2
property real centerY: height / 2
property alias fillColor: shapePath.fillColor
property alias strokeColor: shapePath.strokeColor
property alias pressed: tapHandler.pressed
property real fromX: centerX + centerX * Math.cos(from * Math.PI / 180)
property real fromY: centerY + centerY * Math.sin(from * Math.PI / 180)
property real toX: centerX + centerX * Math.cos(to * Math.PI / 180)
property real toY: centerY + centerY * Math.sin(to * Math.PI / 180)
signal clicked()
containsMode: Shape.FillContains
ShapePath {
id: shapePath
fillColor: "#ffe"
strokeColor: "grey"
startX: centerX; startY: centerY
PathLine { x: fromX; y: fromY }
PathArc {
radiusX: centerX; radiusY: centerY
x: toX; y: toY
}
PathLine { x: centerX; y: centerY }
}
TapHandler {
id: tapHandler
onTapped: pizzaPiece.clicked()
}
}
You can Try it Online!
Hey posting this question to learn how to curve a line onclick i.e using path-curve i want to curve a line but not hard-coded it occur on mouse click event like if clicked on (400,320) then line at that position Y position will change (let say +75) as in image it is hard-coded but i want curve onclick wherever mouse click event occur it should curve
Here is my implementation. It's not exactly the shape you want. It seems that you can not get something like your image with PathCurve but surely you have much more control over the outcome with PathCubic.
Canvas {
id: canvas
width: 640; height: 640
contextType: "2d"
Path {
id: myPath
startX: 320; startY: 0
PathCurve { id: curvePoint_top; x: 320; y: curvePoint.y - 80 }
PathCurve { id: curvePoint; x: 320; y: 320 }
PathCurve { id: curvePoint_bottom; x: 320; y: curvePoint.y + 80 }
PathCurve { x: 320; y: 640 }
}
onPaint: {
context.clearRect(0,0,width,height);
context.strokeStyle = Qt.rgba(.4,.6,.8);
context.path = myPath;
context.stroke();
}
MouseArea{
anchors.fill: parent
onClicked: {
curvePoint.x = mouseX;
curvePoint.y = mouseY;
canvas.requestPaint();
}
}
}
I want to create some sort of Vocabulary Trainer.
I have a Card QML File what shoud represent some kind of a record card where you can see the Vocabulary. When you've answered, the card should turn around 180° and a new Word/Text should be visible on it.
So far I've created a Rectangle for the Card and a Transformation for the Rotation split up in two PropertyAnimations.
For the sake of simplicity I just want the animation to happen when I'm clicking on the Card. Then the Card turns from 0 to 90 degrees. Afterwards the text should be changed. And at last the Card should turn from -90 to 0 degrees. So I'm looking for a logic that allows me to execute an animation, changes a property (text) instantly and executing another animation as a sequence.
Here is my Code so far:
import QtQuick 2.2
import QtGraphicalEffects 1.0
Item {
Rectangle {
id: card
anchors.fill: parent
border.width: 1
border.color: "grey"
antialiasing: true
Text {
id: question
text: "test test test"
anchors.centerIn: card
}
transform: Rotation {
id: rotation
origin.x: (card.width / 2)
origin.y: (card.height / 2)
axis {
x: 0
y: 1
z: 0
}
angle: 0
}
MouseArea {
anchors.fill: card
onClicked: {
// Code for Turning Card arround
rotate_away.start()
question.text = "abcabcabc"
rotate_new.start()
}
}
PropertyAnimation {
id: rotate_away
target: rotation
properties: "angle"
from: 0
to: 90
duration: 1000
}
PropertyAnimation {
id: rotate_new
target: rotation
properties: "angle"
from: -90
to: 0
duration: 1000
}
}
}
So the problem is this part:
rotate_away.start()
question.text = "abcabcabc"
rotate_new.start()
The text changes but only the 2'nd animation will be executed.
I tried
while (rotate_away.running) {}
to wait for the 1st animation but then the application gets stuck.
I think the animations should be played sequently by using SequentialAnimation. Please revisit your code as follows:
MouseArea {
anchors.fill: card
onClicked: {
// Code for Turning Card around
// rotate_away.start()
// question.text = "abcabcabc"
// rotate_new.start()
fullRotate.start();
}
}
SequentialAnimation {
id: fullRotate
PropertyAnimation {
id: rotate_away
target: rotation
properties: "angle"
from: 0
to: 90
duration: 1000
}
PropertyAction {
target: question
property: "text"
value: "abcabcabc"
}
PropertyAnimation {
id: rotate_new
target: rotation
properties: "angle"
from: -90
to: 0
duration: 1000
}
}
Also, I recommend Flipable which is meant for flipping effects.