I have a qtchart with a lineseries with 5 points on the graph. I can drag these points on in both x and y axis inside min and max of axisX and axisY.
How can disable drag of points in the x axis? so it is possible to drag points vertically on the chart. Here is my code:
ChartView {
id: chart
property var selectedPoint: undefined
anchors.fill: parent
antialiasing: true
property real toleranceX: 0.05
property real toleranceY: 0.05
ValueAxis {
id: axisX
min: 0
max: 5
}
ValueAxis {
id: axisY
min: -10
max: 10
}
LineSeries {
id: series1
axisX: axisX
axisY: axisY
pointsVisible: true
}
MouseArea {
anchors.fill: parent
onPressed:
{
var cp = chart.mapToValue(Qt.point(mouse.x,mouse.y));
for(var i = 0;i < series1.count;i ++)
{
var p = series1.at(i);
if(Math.abs(cp.x - p.x) <= chart.toleranceX && Math.abs(cp.y - p.y) <= chart.toleranceY)
{
chart.selectedPoint = p;
break;
}
}
}
onPositionChanged: {
if(chart.selectedPoint != undefined) {
var p = Qt.point(mouse.x, mouse.y);
var cp = chart.mapToValue(p);
if(cp.x >= axisX.min && cp.x <= axisX.max && cp.y >= axisY.min && cp.y <= axisY.max) {
series1.replace(chart.selectedPoint.x, chart.selectedPoint.y, cp.x, cp.y);
chart.selectedPoint = cp;
}
}
}
onReleased: {
chart.selectedPoint = undefined;
}
}
}
// Add data to the series
Component.onCompleted: {
for (var i = 0; i <= 5; i++) {
series1.append(i, Math.random());
}
}
So far I could disable drag on x axis for one point in this case point on 3 by changing onPositionChanged to following code but there are two problems, first this is just one point, second, it does not disable the drag completely instead the point can be dragged on x axis between 2 to 4 but it cannot be dragged outside this range.
onPositionChanged: {
if(chart.selectedPoint != undefined) {
var p = Qt.point(mouse.x, mouse.y);
var cp = chart.mapToValue(p);
if(cp.x >= axisX.min && cp.x <= axisX.max && cp.y >= axisY.min && cp.y <= axisY.max) {
if (Math.round(cp.x) == 3){
series1.replace(chart.selectedPoint.x, chart.selectedPoint.y, cp.x, cp.y);
chart.selectedPoint = cp;
}
}
}
}
Related
My implementation is to keep track of whether I have placed the dot already •. I am trying to acheive the following: A • D • G • J • M • P • S • V • Z. The property binding for check, somehow it doesn't want to go back to 0. I also tried using a local var, but it will always be 0 or 1. How can I resolve this? I get the following error: Binding loop detected for property "text"
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Rectangle {
id: root
visible: true
anchors.fill: parent
property string letters: "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
property int check: 0
readonly property real dx: 20
function midpoint(idx) {
return 20 + idx * root.dx;
}
function newLetters(index) {
if(root.letters[index] === "A"){
root.check = 0
return true
}
if(root.letters[index] === "D"){
root.check = 0
return true
}
if(root.letters[index] === "G"){
root.check = 0
return true
}
if(root.letters[index] === "J"){
root.check = 0
return true
}
if(root.letters[index] === "M"){
root.check = 0
return true
}
if(root.letters[index] === "P"){
root.check = 0
return true
}
if(root.letters[index] === "S"){
root.check = 0
return true
}
if(root.letters[index] === "V"){
root.check = 0
return true
}
if(root.letters[index] === "Z"){
root.check = 0
return true
}
else {
root.check = root.check + 1
}
}
Repeater {
model: 26
Text {
anchors.horizontalCenter: parent.left
anchors.horizontalCenterOffset: root.midpoint(index)
anchors.verticalCenter: parent.verticalCenter
text: root.newLetters(index) ? root.letters[index] : (root.check === 0 ? " • " : "")
}
}
}
}
Yes, by changing the root.check variable, the QQmlEngine will re-evaluate all bindings dependent on, including the newLetters function itself, thus leading to a binding loop.
I have been watching your recent questions (wondering what the end-goal is) and I think you should alter your newLetters function, to return the actual wanted text instead of the condition:
function newLetters(index) {
if(index < 24) //below Y, everything is regular
{
if(index % 3 == 0)
return root.letters[index]
if(index % 3 == 1)
return " ° "
}
else if(index == 24) //Y
{
return ""
}
else if(index == 25) //Z
{
return root.letters[index]
}
return ""
}
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'm trying to reorder Repeater items using drag & drop. I'm using a Repeater due to dynamically loaded data. Below is what I have so far using a simple sqlite database with sample data added. What I'm hoping for is to get the re-order set up so when the user releases the dragged object, the database is updated to reflect the new order. The "Change" button reorders the elements as I want, I just can't get it to work property with drag and drop.
The loadData() function simply creates the table and inserts sample data. The "Change" button won't be in the final code, I just wanted to use it to get the re-ordering code to work.
import QtQuick.Controls 2.2 as QC2
import QtQuick 2.10
import QtQuick.Window 2.10
import QtQuick.LocalStorage 2.0
Window {
id: root
width: 320
height: 480
property var db
property int rowHeight: 90
Component.onCompleted: {
db = LocalStorage.openDatabaseSync("test_db", "1.0", "Database", 100000)
loadData()
}
property var sql_data: []
function loadData() {
var sql, rs, len, i
db.transaction(function(tx) {
tx.executeSql("DROP TABLE IF EXISTS tbl")
tx.executeSql("CREATE TABLE IF NOT EXISTS tbl (
rowId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL, listOrder INTEGER NOT NULL)")
len = 5
for (i = 0; i < len; i++)
tx.executeSql("INSERT INTO tbl (name, listOrder) VALUES ('Item " + i + "', " + i + ")")
rs = tx.executeSql("SELECT name, listOrder FROM tbl ORDER BY listOrder ASC")
})
len = rs.rows.length
sql_data = new Array(len)
for (i = 0; i < len; i++) {
sql_data[i] = {"name": "", "order": ""}
sql_data[i]["name"] = rs.rows.item(i).name
sql_data[i]["order"] = rs.rows.item(i).listOrder
}
repeater.model = sql_data
}
Flickable {
id: mainFlick
anchors.fill: parent
contentHeight: mainColumn.height
Column {
id: mainColumn
width: parent.width
QC2.Button {
text: "Change"
onClicked: {
var p1, p2
var d1 = new Date().getTime()
var movedEle = 3
var newPos = 1
var ele1 = repeater.itemAt(movedEle)
var ele2 = repeater.itemAt(newPos)
ele1.y = ele2.y
root.sql_data[movedEle]["order"] = root.sql_data[newPos]["order"]
if (newPos < movedEle) {
p1 = newPos
p2 = movedEle
} else {
p1 = movedEle
p2 = newPos
}
root.db.transaction(function(tx) {
var sql = "UPDATE tbl SET listOrder = " + root.sql_data[p1]["order"] + " "
sql += "WHERE listOrder = " + (root.sql_data[p2]["order"])
for (var i = p1; i < p2; i++) {
if (i !== movedEle) {
repeater.itemAt(i).y = repeater.itemAt(i).y + rowHeight
root.sql_data[i]["order"] += 1
sql = "UPDATE tbl SET listOrder = " + root.sql_data[i]["order"] + " "
sql += "WHERE listOrder = " + (root.sql_data[i]["order"] - 1)
tx.executeSql(sql)
}
}
})
sql_data = sql_data.sort(function(a,b) {
return a["order"] - b["order"]
})
repeater.model = sql_data
var d2 = new Date().getTime()
console.debug("Seconds: " + (d2 - d1) / 1000)
}
}
Repeater {
id: repeater
width: parent.width
model: []
delegate: Column {
id: repeaterItem
width: parent.width - 20
height: rowHeight
anchors.horizontalCenter: parent.horizontalCenter
property int pos: index
DropArea {
id: dragTarget
width: parent.width
height: 20
Rectangle {
id: dropRect
anchors.fill: parent
color: "green"
states: [
State {
when: dragTarget.containsDrag
PropertyChanges {
target: dropRect
color: "red"
}
}
]
}
}
Row {
id: contentRow
width: parent.width
height: parent.height
z: 1
Column {
id: itemColumn
width: parent.width - 70
Text {
text: "Name: " + modelData["name"]
}
Text {
text: "Order: " + modelData["order"]
}
Text {
text: "Repeater Index: " + index
}
} // itemColumn
MouseArea {
id: dragArea
width: 40
height: itemColumn.height
drag.axis: Drag.YAxis
drag.target: dragRect
onReleased: parent = ((dragRect.Drag.target !== null) ? dragRect.Drag.target : root)
Rectangle {
Component.onCompleted: console.debug(dragRect.Drag.target)
id: dragRect
width: 40
height: itemColumn.height
color: "grey"
Drag.active: dragArea.drag.active
Drag.source: dragArea
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
states : State {
when: dragArea.drag.active
ParentChange { target: dragRect; parent: root }
AnchorChanges {
target: dragRect
anchors.verticalCenter: undefined
anchors.horizontalCenter: undefined
}
}
}
}
} // contentRow
Behavior on y {
PropertyAnimation {
duration: 200
easing.type: Easing.Linear
}
}
} // repeaterItem
} // repeater
} // mainColumn
} // mainFlick
} // id
Much of the drag and drop code came from the Qt Examples site.
Here's what my problems are:
The MouseArea used for dragging the rectangles doesn't seem to change locations. Once a rectangle is actually moved, it stays in its new location but if I want to move it again, I have to click and drag where it was originally when the app loads.
If I switch the target of the Drag Area from the child rectangle to the entire row (repeaterItem), everything moves properly but will no longer interact with the Drop areas.
I think I can get the index of the new location after a row has been dragged simply by getting the y-value of the Drop Area. Would there be a better way to do this?
Since the "Change" button already re-orders the row elements, I only need help getting the rows to properly interact with the Drop Areas and then get the y-position of the Drop Area when the dragged item is released.
It may not be the most efficient out there but it's a good start for anybody looking to do something similar.
import QtQuick.Controls 2.2 as QC2
import QtQuick 2.10
import QtQuick.Window 2.10
import QtQuick.LocalStorage 2.0
Window {
id: root
width: 320
height: 580
property var db
property int rowHeight: 90
property int len
Component.onCompleted: {
db = LocalStorage.openDatabaseSync("test_db", "1.0", "Database", 100000)
loadData()
}
property var sql_data: []
function loadData() {
var sql, rs, i
db.transaction(function(tx) {
tx.executeSql("DROP TABLE IF EXISTS tbl")
tx.executeSql("CREATE TABLE IF NOT EXISTS tbl (
rowId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL, listOrder INTEGER NOT NULL)")
len = 15
for (i = 0; i < len; i++)
tx.executeSql("INSERT INTO tbl (name, listOrder) VALUES ('Item " + i + "', " + i + ")")
rs = tx.executeSql("SELECT name, listOrder FROM tbl ORDER BY listOrder ASC")
})
len = rs.rows.length
sql_data = new Array(len)
for (i = 0; i < len; i++) {
sql_data[i] = {"name": "", "order": ""}
sql_data[i]["name"] = rs.rows.item(i).name
sql_data[i]["order"] = rs.rows.item(i).listOrder
}
repeater.model = sql_data
}
Flickable {
id: mainFlick
anchors.fill: parent
contentHeight: mainColumn.height
rebound: Transition {
NumberAnimation {
properties: "y"
duration: 500
easing.type: Easing.OutCirc
}
}
Column {
id: mainColumn
width: parent.width
function moveEle(start, end, f) {
if (f === false) {
var p1, p2, ele1, ele2
var c = false
var d1 = new Date().getTime()
ele1 = repeater.itemAt(start)
console.debug(f)
ele2 = repeater.itemAt(end)
root.sql_data[start]["order"] = root.sql_data[end]["order"]
if (end < start) {
p1 = end
p2 = start
} else {
p1 = start
p2 = end
c = true
}
root.db.transaction(function(tx) {
var sql = "UPDATE tbl SET listOrder = " + root.sql_data[p1]["order"] + " "
sql += "WHERE listOrder = " + (root.sql_data[p2]["order"])
for (var i = p1; i < p2; i++) {
if (c === false) {
if (i !== start) {
root.sql_data[i]["order"]++
sql = "UPDATE tbl SET listOrder = " + root.sql_data[i]["order"] + " "
sql += "WHERE listOrder = " + (root.sql_data[i]["order"] - 1)
tx.executeSql(sql)
}
} else {
root.sql_data[i]["order"]--
sql = "UPDATE tbl SET listOrder = " + root.sql_data[i]["order"] + " "
sql += "WHERE listOrder = " + (root.sql_data[i]["order"] - 1)
tx.executeSql(sql)
}
}
})
} else if (f === true) {
c = false
d1 = new Date().getTime()
ele1 = repeater.itemAt(start)
console.debug(f)
end--
ele2 = repeater.itemAt(end)
root.sql_data[start]["order"] = root.sql_data[end]["order"]
p1 = start
p2 = end
c = true
root.db.transaction(function(tx) {
var sql = "UPDATE tbl SET listOrder = " + root.sql_data[p1]["order"] + " "
sql += "WHERE listOrder = " + (root.sql_data[p2]["order"])
tx.executeSql(sql)
for (var i = p1; i <= p2; i++) {
if (i !== start) {
root.sql_data[i]["order"]--
sql = "UPDATE tbl SET listOrder = " + root.sql_data[i]["order"] + " "
sql += "WHERE listOrder = " + (root.sql_data[i]["order"] - 1)
tx.executeSql(sql)
}
}
})
}
sql_data = sql_data.sort(function(a,b) {
return a["order"] - b["order"]
})
repeater.model = sql_data
var d2 = new Date().getTime()
console.debug("Seconds: " + (d2 - d1) / 1000)
}
Repeater {
id: repeater
width: parent.width
model: []
delegate: Column {
id: repeaterItem
width: parent.width
height: rowHeight
anchors.horizontalCenter: parent.horizontalCenter
z: dragArea.drag.active ? 2 : 1
property int pos: index
DropArea {
id: dragTarget
width: parent.width
height: 15
property int ind: index
onEntered: {
drag.source.ind = index
drag.source.f = false
if (drag.source.ind !== drag.source.oldInd && drag.source.ind !== drag.source.oldInd + 1)
drag.source.caught = true
}
onExited: drag.source.caught = false
Rectangle {
id: dropRect
anchors.fill: parent
z: 0
states: [
State {
when: dragTarget.containsDrag
PropertyChanges {
target: dropRect
color: "grey"
}
PropertyChanges {
target: dropRectLine
visible: false
}
}
]
Rectangle {
id: dropRectLine
width: parent.width
height: 1
anchors.verticalCenter: parent.verticalCenter
color: "black"
}
}
}
Row {
id: contentRow
width: parent.width
height: parent.height
Drag.active: dragArea.drag.active
Drag.source: dragArea
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
Column {
id: itemColumn
width: parent.width - 70
Text {
text: "Name: " + modelData["name"]
}
Text {
text: "Order: " + modelData["order"]
}
Text {
text: "Repeater Index: " + index
}
} // itemColumn
MouseArea {
id: dragArea
width: 40 //repeater.width
height: itemColumn.height
drag.axis: Drag.YAxis
drag.target: contentRow
property point beginDrag
property point dropTarget
property bool caught: false
property int ind
property int oldInd: index
property bool f
onPressed: {
dragArea.beginDrag = Qt.point(contentRow.x, contentRow.y);
}
onReleased: {
if (dragArea.caught) {
dropRectFinal.color = "white"
dropRectLineFinal.visible = true
mainColumn.moveEle(index,dragArea.ind, dragArea.f)
} else {
backAnimY.from = contentRow.y;
backAnimY.to = beginDrag.y;
backAnim.start()
}
}
Rectangle {
id: dragRect
width: 40
height: itemColumn.height
color: "grey"
}
} // contentRow
} // dragArea
ParallelAnimation {
id: backAnim
SpringAnimation { id: backAnimY; target: contentRow; property: "y"; duration: 300; spring: 2; damping: 0.2 }
}
} // repeaterItem
} // repeater
DropArea {
id: dragTargetFinal
width: parent.width
height: 15
property int ind: root.len
onEntered: {
drag.source.ind = ind
drag.source.f = true
if (drag.source.ind !== drag.source.oldInd && drag.source.ind !== drag.source.oldInd + 1)
drag.source.caught = true
}
onExited: drag.source.caught = false
Rectangle {
id: dropRectFinal
anchors.fill: parent
states: [
State {
when: dragTargetFinal.containsDrag
PropertyChanges {
target: dropRectFinal
color: "grey"
}
PropertyChanges {
target: dropRectLineFinal
visible: false
}
}
]
Rectangle {
id: dropRectLineFinal
width: parent.width
height: 1
anchors.verticalCenter: parent.verticalCenter
color: "black"
}
}
}
} // mainColumn
QC2.ScrollBar.vertical: QC2.ScrollBar { active: scrollAnim.running ? true : false }
} // mainFlick
DropArea {
id: scrollUp
width: parent.width
height: 50
anchors.top: parent.top
visible: {
var visible = false
if (mainFlick.contentY > 0)
visible = true
visible
}
onEntered: {
scrollAnim.from = mainFlick.contentY
scrollAnim.to = 0
scrollAnim.start()
}
onExited: scrollAnim.stop()
}
DropArea {
id: scrollDown
width: parent.width
height: 50
anchors.bottom: parent.bottom
visible: {
var visible = false
if (mainFlick.contentY < mainColumn.height - Window.height)
visible = true
visible
}
onEntered: {
scrollAnim.from = mainFlick.contentY
scrollAnim.to = mainColumn.height - Window.height
scrollAnim.start()
}
onExited: scrollAnim.stop()
}
SmoothedAnimation {
id: scrollAnim
target: mainFlick
property: "contentY"
velocity: 400
loops: 1
maximumEasingTime: 10
}
} // root
In a touchpanel application, is it possible to edit the integer value of a qml SpinBox, from QtQuickControls 2.0, in such a way that a numeric keypad appears on-screen and one can enter the exact value?
Or do you have any idea on how to embed this predefined number pad in a customized spinbox, that should pop-up when the user taps on the integer number?
The numpad can be set to be invisible and put on top of everything, then you can have a function to enable its visibility and set it's target. When you are done with typing the number, you set the target value to the numpad value, and hide it again. This way you can target different spinboxes.
As of the actual method to request the keypad, you can put a MouseArea to fill the spinbox on top of it. Or you can make the mouse area narrower, so that the plus/minus buttons of the spinbox are still clickable.
Also keep in mind that numpad you have linked is not actually functional.
Thanks for your suggestion. I came up with a first quick solution based on your idea and the number pad from the example section. I post it here, just in case it helps someone else as starting point. I am a QML beginner, so happy to get any improvements or corrections.
See attached screenshot of the numeric virtual touchpad appearing when clicking on the spinbox number
SpinBox {
id: boxCommPos
x: 50
y: 50
z: 0
width: 200
height: 50
to: 360
editable: false
contentItem: TextInput {
z: 1
text: parent.textFromValue(parent.value, parent.locale)
font: parent.font
//color: "#21be2b"
//selectionColor: "#21be2b"
//selectedTextColor: "#ffffff"
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
readOnly: !parent.editable
validator: parent.validator
inputMethodHints: Qt.ImhFormattedNumbersOnly
}
Label {
id: commUnits
x: 125
y: 0
z: 3
text: qsTr("º")
font.pointSize: 16
anchors.right: parent.right
anchors.rightMargin: 45
}
MouseArea {
id: padCommPos
x: 40
y: 0
z: 2
width: parent.width-x-x
height: 50
onClicked: touchpad.visible=true
}
}
NumberTouchpad {
id: touchpad
x: 470
y: -20
z: 1
scale: 0.90
visible: false
Rectangle {
id: backrect
x: -parent.x/parent.scale
z: -1
width: parent.parent.width/parent.scale
height: parent.parent.height/parent.scale
color: "#eceeea"
opacity: 0.5
MouseArea {
anchors.fill: parent
}
}
}
The file NumberPad.qml
import QtQuick 2.0
Grid {
columns: 3
columnSpacing: 32
rowSpacing: 16
signal buttonPressed
Button { text: "7" }
Button { text: "8" }
Button { text: "9" }
Button { text: "4" }
Button { text: "5" }
Button { text: "6" }
Button { text: "1" }
Button { text: "2" }
Button { text: "3" }
Button { text: "0" }
Button { text: "."; dimmable: true }
//Button { text: " " }
Button { text: "±"; color: "#6da43d"; operator: true; dimmable: true }
//Button { text: "−"; color: "#6da43d"; operator: true; dimmable: true }
//Button { text: "+"; color: "#6da43d"; operator: true; dimmable: true }
//Button { text: "√"; color: "#6da43d"; operator: true; dimmable: true }
//Button { text: "÷"; color: "#6da43d"; operator: true; dimmable: true }
//Button { text: "×"; color: "#6da43d"; operator: true; dimmable: true }
Button { text: "C"; color: "#6da43d"; operator: true }
Button { text: "✔"; color: "#6da43d"; operator: true; dimmable: true }
Button { text: "X"; color: "#6da43d"; operator: true }
}
Display.qml
// Copyright (C) 2015 The Qt Company Ltd.
import QtQuick 2.0
import QtQuick.Window 2.0
Item {
id: display
property real fontSize: Math.floor(Screen.pixelDensity * 10.0)
property string fontColor: "#000000"
property bool enteringDigits: false
property int maxDigits: (width / fontSize) + 1
property string displayedOperand
property string errorString: qsTr("ERROR")
property bool isError: displayedOperand === errorString
function displayOperator(operator)
{
listView.model.append({ "operator": operator, "operand": "" })
enteringDigits = true
listView.positionViewAtEnd()
//console.log("display",operator);
}
function newLine(operator, operand)
{
displayedOperand = displayNumber(operand)
listView.model.append({ "operator": operator, "operand": displayedOperand })
enteringDigits = false
listView.positionViewAtEnd()
//console.log("newLine",operator);
}
function appendDigit(digit)
{
if (!enteringDigits)
listView.model.append({ "operator": "", "operand": "" })
var i = listView.model.count - 1;
listView.model.get(i).operand = listView.model.get(i).operand + digit;
enteringDigits = true
listView.positionViewAtEnd()
//console.log("num is ", digit);
}
function setDigit(digit)
{
var i = listView.model.count - 1;
listView.model.get(i).operand = digit;
listView.positionViewAtEnd()
//console.log("setDigit",digit);
}
function clear()
{
displayedOperand = ""
if (enteringDigits) {
var i = listView.model.count - 1
if (i >= 0)
listView.model.remove(i)
enteringDigits = false
}
//console.log("clearing");
}
// Returns a string representation of a number that fits in
// display.maxDigits characters, trying to keep as much precision
// as possible. If the number cannot be displayed, returns an
// error string.
function displayNumber(num) {
if (typeof(num) != "number")
return errorString;
var intNum = parseInt(num);
var intLen = intNum.toString().length;
// Do not count the minus sign as a digit
var maxLen = num < 0 ? maxDigits + 1 : maxDigits;
if (num.toString().length <= maxLen) {
if (isFinite(num))
return num.toString();
return errorString;
}
// Integer part of the number is too long - try
// an exponential notation
if (intNum == num || intLen > maxLen - 3) {
var expVal = num.toExponential(maxDigits - 6).toString();
if (expVal.length <= maxLen)
return expVal;
}
// Try a float presentation with fixed number of digits
var floatStr = parseFloat(num).toFixed(maxDigits - intLen - 1).toString();
if (floatStr.length <= maxLen)
return floatStr;
return errorString;
}
Item {
id: theItem
width: parent.width
height: parent.height
Rectangle {
id: rect
x: 0
color: "#eceeea"
height: parent.height
width: display.width
}
/*Image {
anchors.right: rect.left
source: "images/paper-edge-left.png"
height: parent.height
fillMode: Image.TileVertically
}
Image {
anchors.left: rect.right
source: "images/paper-edge-right.png"
height: parent.height
fillMode: Image.TileVertically
}
Image {
id: grip
source: "images/paper-grip.png"
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 20
}*/
ListView {
id: listView
width: display.width
height: display.height
delegate: Item {
height: display.fontSize * 1.1
width: parent.width
Text {
id: operator
font.pixelSize: display.fontSize
color: "#6da43d"
text: model.operator
}
Text {
id: operand
y:5
font.pixelSize: display.fontSize
color: display.fontColor
anchors.right: parent.right
anchors.rightMargin: 22
text: model.operand
}
}
model: ListModel { }
}
}
}
calculator.qs
// Copyright (C) 2015 The Qt Company Ltd.
var curVal = 0
var memory = 0
var lastOp = ""
var previousOperator = ""
var digits = ""
function disabled(op) {
if (op == "✔")
display.fontColor="#000000"
if (op=="X")
return false
else if (op == "✔" && (digits.toString().search(/\./) != -1 || digits.toString().search(/-/)!= -1 || parseInt(digits)>359)) {
display.fontColor="#ff0000"
return true
}
else if (digits == "" && !((op >= "0" && op <= "9") || op == "."))
return true
else if (op == '=' && previousOperator.length != 1)
return true
else if (op == "." && digits.toString().search(/\./) != -1) {
return true
} else if (op == "√" && digits.toString().search(/-/) != -1) {
return true
} else {
return false
}
}
function digitPressed(op)
{
if (disabled(op))
return
if (digits.toString().length >= display.maxDigits)
return
if (lastOp.toString().length == 1 && ((lastOp >= "0" && lastOp <= "9") || lastOp == ".") ) {
digits = digits + op.toString()
display.appendDigit(op.toString())
} else {
digits = op
display.appendDigit(op.toString())
}
lastOp = op
}
function operatorPressed(op)
{
if (disabled(op))
return
lastOp = op
if (op == "±") {
digits = Number(digits.valueOf() * -1)
display.setDigit(display.displayNumber(digits))
return
}
if (previousOperator == "+") {
digits = Number(digits.valueOf()) + Number(curVal.valueOf())
} else if (previousOperator == "−") {
digits = Number(curVal.valueOf()) - Number(digits.valueOf())
} else if (previousOperator == "×") {
digits = Number(curVal) * Number(digits.valueOf())
} else if (previousOperator == "÷") {
digits = Number(curVal) / Number(digits.valueOf())
}
if (op == "+" || op == "−" || op == "×" || op == "÷") {
previousOperator = op
curVal = digits.valueOf()
digits = ""
display.displayOperator(previousOperator)
return
}
if (op == "=") {
display.newLine("=", digits.valueOf())
}
curVal = 0
previousOperator = ""
if (op == "1/x") {
digits = (1 / digits.valueOf()).toString()
} else if (op == "x^2") {
digits = (digits.valueOf() * digits.valueOf()).toString()
} else if (op == "Abs") {
digits = (Math.abs(digits.valueOf())).toString()
} else if (op == "Int") {
digits = (Math.floor(digits.valueOf())).toString()
} else if (op == "√") {
digits = Number(Math.sqrt(digits.valueOf()))
display.newLine("√", digits.valueOf())
} else if (op == "mc") {
memory = 0;
} else if (op == "m+") {
memory += digits.valueOf()
} else if (op == "mr") {
digits = memory.toString()
} else if (op == "m-") {
memory = digits.valueOf()
} else if (op == "backspace") {
digits = digits.toString().slice(0, -1)
display.clear()
display.appendDigit(digits)
} else if (op == "✔") {
window.visible=false
boxCommPos.value=parseInt(digits)
display.clear()
curVal = 0
memory = 0
lastOp = ""
digits = ""
} else if (op == "X") {
window.visible=false
display.clear()
curVal = 0
memory = 0
lastOp = ""
digits = ""
}
// Reset the state on 'C' operator or after
// an error occurred
if (op == "C" || display.isError) {
display.clear()
curVal = 0
memory = 0
lastOp = ""
digits = ""
}
}
Button.qml
// Copyright (C) 2015 The Qt Company Ltd.
import QtQuick 2.0
Item {
id: button
property alias text: textItem.text
property color color: "#eceeea"
property bool operator: false
property bool dimmable: false
property bool dimmed: false
width: 30
height: 50
Text {
id: textItem
font.pixelSize: 48
wrapMode: Text.WordWrap
lineHeight: 0.75
color: (dimmable && dimmed) ? Qt.darker(button.color) : button.color
Behavior on color { ColorAnimation { duration: 120; easing.type: Easing.OutElastic} }
states: [
State {
name: "pressed"
when: mouse.pressed && !dimmed
PropertyChanges {
target: textItem
color: Qt.lighter(button.color)
}
}
]
}
MouseArea {
id: mouse
anchors.fill: parent
anchors.margins: -5
onClicked: {
if (operator)
window.operatorPressed(parent.text)
else
window.digitPressed(parent.text)
}
}
function updateDimmed() {
dimmed = window.isButtonDisabled(button.text)
}
Component.onCompleted: {
numPad.buttonPressed.connect(updateDimmed)
updateDimmed()
}
}
I have a curve running above or below a sloped line and I want to fill the area just above the line and below the curve, but not the area below the line and above the curve, as illustrated in the left image.
Again, it is easy to fill like right image, but I want to fill like in left image. So, how to get rid of the unwanted fill?
svg.append("path").attr("class", "line").attr("d", peak(pp)).style({ fill: peakColor, opacity: 0.5 });
Okay, this is something I came up with for me in case anybody cares about it.
var slope = (dp.rightY - dp.leftY) / (dp.rightX - dp.leftX); // slope of the line
var pp = [];
pp.push({ x: dp.leftX, y: dp.leftY });
var wasAbove = true; // track if it's above the line
data.forEach(function (d) {
if (d.x >= dp.leftX && d.x <= dp.rightX) {
var yAtLine = (d.x - dp.leftX) * slope + dp.leftY;
if (d.y > yAtLine) {
if (!wasAbove)
pp.push({ x: d.x, y: yAtLine });
pp.push(d);
wasAbove = true;
} else if (wasAbove) {
pp.push({ x: d.x, y: yAtLine });
wasAbove = false;
}
}
});
pp.push({ x: dp.rightX, y: dp.rightY });
var peak = d3.svg.line().x(function (d) { return xScale(d.x) }).y(function (d) { return yScale(d.y) });
svg.append("path").attr("class", "line").attr("d", peak(pp)).style({ fill: peakColor, opacity: 0.5 });