Customize Bar-series and BarCategoryAxis in QML - qt

I need to customize the BarCategoryAxis labels and BarSeries. Following is the code:
import QtQuick 2.6
import QtQuick.Window 2.2
import QtQuick.Controls 2.0
import QtCharts 2.2
ApplicationWindow {
id: window
width: 640
height: 480
visible: true
ChartView {
id: chartview
width: parent.width
height: 300
BarSeries {
name: "BarSeries"
axisX: BarCategoryAxis {
id:barAxis
property int minCategory: 10
property int maxCategory: 100
property int category: 50
categories: ["10","20","30","40","50","60","70","80","90","100"]
min: Math.floor(((minCategory + sb.position *
(maxCategory - minCategory - category)) + 9)/10)*10
max: Math.floor((((minCategory + sb.position *
(maxCategory - minCategory - category)) + category) + 9)/10) *10
}
axisY: ValueAxis {
id:axisY
min: 0
max: 70
tickCount: 5
labelFormat: "%.0f"
}
BarSet { values: [2, 2, 3, 4, 5, 6,7] }
BarSet { values: [5, 1, 2, 4, 1, 7] }
BarSet { values: [3, 5, 8, 13, 5, 8] }
}
}
Slider {
id: sb
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
height: 30
}
}
1) How to align the X Axis labels exactly below the grid? currently, it is at center as below:
I Tried with ValueAxis but it will not plot the bars properly but labels will be exactly below the grid.
2) Currently, slider moves the graph but not fluent. Can anyone suggest a better approach?

1) Did you try with CategoryAxis instead of BarCategoryAxis, it has an attribute labelsPosition.
2) First you should make your computation inside the Slider slot :
Slider {
onPositionChanged: {
barAxis.min = ...
barAxis.max = ...
}
}
Then I suggest to use ChartView functions like scrollRight(...), it's much more smooth.

Related

how to add PathLine to ShapePath with a Repeater?

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.

How to add text in plotting area of chartview in qml

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;
}
}

How can I get pixel array from qml canvas?

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

Customizing BarSet in QML Charts

I am trying to change the look of a BarChart using QtCharts 2.1. At the moment I apply the desired color and styling to the single QBarSets like this:
import QtQuick 2.8
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0
import QtCharts 2.1
import QtQuick.Window 2.2
ChartView {
id: root
width: 600
height: 400
antialiasing: true
backgroundColor: "transparent"
legend.visible: false
property var histogramProvider
Connections {
target: histogramProvider
onHistogramChanged: {
console.log("histogram changed")
histogramProvider.updateSeries(histogramSeries)
console.log(histogramSeries)
console.log(histogramSeries.at(0))
histogramSeries.at(0).color = Qt.rgba(0.9, 0.2, 0.1, 0.5)
}
onAxesChanged: {
xAxis.min = xMin
xAxis.max = xMax
yAxis.min = yMin
yAxis.max = yMax
}
}
ValueAxis {
id: yAxis
min: 0
max: 100
tickCount: 3
gridVisible: false
labelsColor: Style.fontColorLight
labelsFont: Qt.font({pointSize: 8})
titleText: qsTr("Count")
labelFormat: "%.1f"
}
ValueAxis {
id: xAxis
tickCount: 3
gridVisible: false
labelsColor: "white"
labelFormat: "%i"
min: 0
max: 100
titleText: qsTr("Elongation")
}
StackedBarSeries {
id: histogramSeries
axisX: xAxis
axisY: yAxis
}
}
I can see the BarSets with the default colors in the chart, hwever I can not change the colors. The code produces the following console output from onHistogramChanged:
qml: QtCharts::DeclarativeStackedBarSeries(0x1df550cd830)
qml: null
qrc:/qml/Histogram.qml:27: TypeError: Type error
I update the BarSeries from C++ like this:
void HistogramProvider::updateSeries(QStackedBarSeries *barSeries)
{
[...]
barSeries->clear();
QBarSet* set = new QBarSet("Histogram", barSeries);
set->setBorderColor(QColor("transparent"));
set->setColor(QColor("#6f74dd"));
for(int i = 0; i < bins.size(); i++) {
set->append(bins.at(i));
}
qDebug() << "blubb";
barSeries->append(set);
}
What am I doing wrong here?

Wrong coordinates white getting real position of item relative to its parent

I have simple scene with only 2 Rectangles. The difference is that first one uses absolute coordinates and second one uses anchors. In this case both of rectangles are placed on the same place. But I get different coordinates at all.
import QtQuick 2.4
import QtQuick.Window 2.2
Window {
visible: true
width: 600
height: 600
Rectangle {
id: rec1
x: 200
y: 200
width: 200
height: 200
color: "green"
opacity: 0.5
Component.onCompleted: console.log("rec1: " + rec1.x + "," + rec1.y);
}
Rectangle {
id: rec2
anchors.centerIn: parent
width: 200
height: 200
color: "blue"
opacity: 0.5
Component.onCompleted: console.log("rec2: " + rec2.x + "," + rec2.y);
}
}
The output:
qml: rec2: -100,-100
qml: rec1: 200,200
Yes, I know that it's not really "wrong" result, but how can I get real item coordinates relative to its parent for both of rectangles, i.e. (200,200)?
The documentation of Item proposes the mapToItem function:
Maps the point (x, y) or rect (x, y, width, height), which is in this
item's coordinate system, to item's coordinate system, and returns an
object with x and y (and optionally width and height) properties
matching the mapped coordinate.
If item is a null value, this maps the point or rect to the coordinate
system of the root QML view.
Since the coordinate must be in item' system, the correct way to call the function in your case would be:
<item_id>.mapToItem(<parent_id>, 0, 0)
where (0, 0) is the origin of <item_id> coordinates system.
Since in this case the parent is not an Item itself, we can exploit the null version of the method described by documentation and write:
<item_id>.mapToItem(null, 0, 0)
That's the theory. However, in this particular case (as noted by others), the layout management has not set the coordinate properties yet and thus the methods fail. That seems to be related to the non-consistent state in which items fall during initialisation. Indeed, if we use the function in the onDestruction handler, i.e. when we are sure that initialisation has finished, they give the expected results. See your modified code below:
import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.Controls 1.3
Window {
visible: true
width: 600
height: 600
Rectangle {
id: rec1
x: 200
y: 200
width: 200
height: 200
color: "green"
opacity: 0.5
}
Rectangle {
id: rec2
width: 200
height: 200
anchors.centerIn: parent
color: "blue"
opacity: 0.5
}
Component.onCompleted: {
console.info("NOPE! :(")
var cords = rec1.mapToItem(null, 0, 0)
console.info("rec1: " + cords.x + " " + cords.y)
cords = rec2.mapToItem(null, 0, 0)
console.info("rec2: " + cords.x + " " + cords.y)
}
Component.onDestruction: {
console.info("YES! :)")
var cords = rec1.mapToItem(null, 0, 0)
console.info("rec1: " + cords.x + " " + cords.y)
cords = rec2.mapToItem(null, 0, 0)
console.info("rec2: " + cords.x + " " + cords.y)
cords = rec2.mapToItem(null, 100, 100) // (100, 100) of second rec is...
console.info("rec2: " + cords.x + " " + cords.y) // correctly (300, 300) !!
}
}
Output:
qml: NOPE! :(
qml: rec1: 200 200
qml: rec2: -100 -100
qml: YES! :)
qml: rec1: 200 200
qml: rec2: 200 200
qml: rec2: 300 300
Both rectangles have same coordinates but on different time:
import QtQuick 2.4
import QtQuick.Window 2.2
Window {
visible: true
width: 600
height: 600
Rectangle {
id: rec1
x: 200
y: 200
width: 200
height: 200
color: "green"
opacity: 0.5
Component.onCompleted: console.log("rec1: " + rec1.x + "," + rec1.y);
}
Rectangle {
id: rec2
anchors.centerIn: parent
width: 200
height: 200
color: "blue"
opacity: 0.5
Component.onCompleted: console.log("rec2: " + rec2.x + "," + rec2.y);
onXChanged: console.log("rec2.x: " + rec2.x);
onYChanged: console.log("rec2.y: " + rec2.y);
}
}

Resources