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?
Related
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.
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
How to change color of one particular point in chartview in qml.i.e now all x axis values are displaying in black color but i want odd number to be displayed in red color.I want as shown in image where label colors in y axis are of different colors.
here is my piece of code
ChartView {
id:chartView
width: 2*horizontalList.width
height:horizontalList.height
antialiasing: true
animationOptions: ChartView.SeriesAnimations
legend.visible:false
ValueAxis {
id: scaleAxisX
min: 0
max: pointsX.length
tickCount: pointsX.length+1
labelFormat: "%.0f"
titleVisible: false
gridVisible: true
}
ValueAxis {
id: scaleAxisY
min: 0
max: 1.0
tickCount: 6
//labelFormat: "%.0f"
titleVisible: false
gridVisible: false
labelsVisible: false
labelsColor: "#757575"
}
ScatterSeries {
id: scatterSeries
axisXTop: scaleAxisX
axisY: scaleAxisY
color: "black"
markerSize: 12
}
}
As shown in second image ,the top line with red circle is a X axis(valueAxis) of chartview.i want to make the number inside the red circle to be in red color.
You can add a different ScatterSeries with color value;
import QtQuick 2.0
import QtCharts 2.0
ChartView {
title: "Scatters"
anchors.fill: parent
antialiasing: true
ScatterSeries {
color: "black"
id: scatter1
name: "Scatter1"
XYPoint { x: 1; y: 1 }
XYPoint { x: 2; y: 2 }
XYPoint { x: 3; y: 3 }
XYPoint { x: 4; y: 4 }
}
ScatterSeries {
color: "red"
name: "Scatter2"
XYPoint { x: 1.5; y: 1.5 }
}
}
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.