smart-meter support done

This commit is contained in:
Michael Zanetti 2018-10-29 22:41:45 +01:00
parent a3a33595d5
commit a974096dbd
9 changed files with 351 additions and 55 deletions

View File

@ -163,13 +163,13 @@ void LogsModelNg::setViewStartTime(const QDateTime &viewStartTime)
QVariant LogsModelNg::minValue() const
{
qDebug() << "returning min value" << m_minValue;
// qDebug() << "returning min value" << m_minValue;
return m_minValue;
}
QVariant LogsModelNg::maxValue() const
{
qDebug() << "returning max value" << m_maxValue;
// qDebug() << "returning max value" << m_maxValue;
return m_maxValue;
}
@ -177,8 +177,6 @@ void LogsModelNg::logsReply(const QVariantMap &data)
{
// qDebug() << "logs reply" << data;
m_busy = false;
emit busyChanged();
int offset = data.value("params").toMap().value("offset").toInt();
int count = data.value("params").toMap().value("count").toInt();
@ -221,20 +219,20 @@ void LogsModelNg::logsReply(const QVariantMap &data)
if (i > 0) {
LogEntry *newerEntry = newBlock.at(i - 1);
if (newerEntry->value().toBool() != entry->value().toBool()) {
qDebug() << "Adding bool line series point:" << (newerEntry->timestamp().addSecs(-1)) << newerEntry->timestamp().addSecs(-1).toMSecsSinceEpoch() << (entry->value().toBool() ? 1 : 0) << "(correction)";
// qDebug() << "Adding bool line series point:" << (newerEntry->timestamp().addSecs(-1)) << newerEntry->timestamp().addSecs(-1).toMSecsSinceEpoch() << (entry->value().toBool() ? 1 : 0) << "(correction)";
m_graphSeries->append(QPointF(newerEntry->timestamp().addSecs(-1).toMSecsSinceEpoch(), entry->value().toBool() ? 1 : 0));
}
}
if (m_graphSeries->count() == 0) {
// If it's the first one, make sure we add an ending point at 1
qDebug() << "Adding bool line series point:" << QDateTime::currentDateTime() << QDateTime::currentDateTime().toMSecsSinceEpoch() - 1 << (entry->value().toBool() ? 1 : 0) << "(beginning)";
// qDebug() << "Adding bool line series point:" << QDateTime::currentDateTime() << QDateTime::currentDateTime().toMSecsSinceEpoch() - 1 << (entry->value().toBool() ? 1 : 0) << "(beginning)";
m_graphSeries->append(QPointF(QDateTime::currentDateTime().toMSecsSinceEpoch(), 1));
m_graphSeries->append(QPointF(QDateTime::currentDateTime().toMSecsSinceEpoch(), entry->value().toBool() ? 1 : 0));
} else if (i == 0) {
// Adding a new batch... remove the last appended 1 from the previous batch
m_graphSeries->remove(m_graphSeries->count() - 1);
}
qDebug() << "Adding bool line series point:" << entry->timestamp() << entry->timestamp().toMSecsSinceEpoch() << (entry->value().toBool() ? 1 : 0);
// qDebug() << "Adding bool line series point:" << entry->timestamp() << entry->timestamp().toMSecsSinceEpoch() << (entry->value().toBool() ? 1 : 0);
m_graphSeries->append(QPointF(entry->timestamp().toMSecsSinceEpoch(), entry->value().toBool() ? 1 : 0));
if (i == newBlock.count() - 1) {
// End the batch at 1 again
@ -248,8 +246,10 @@ void LogsModelNg::logsReply(const QVariantMap &data)
// m_graphSeries->append(QPointF(newerEntry->timestamp().toMSecsSinceEpoch() - 1, entry->value().toReal()));
// }
// }
if (m_graphSeries->count() == 0) {
m_graphSeries->insert(0, QPointF(QDateTime::currentDateTime().toMSecsSinceEpoch(), entry->value().toReal()));
qDebug() << "Adding 1st line series point:" << (offset + i) << QDateTime::currentDateTime().toMSecsSinceEpoch() << entry->value().toReal();
m_graphSeries->append(QPointF(QDateTime::currentDateTime().toMSecsSinceEpoch(), entry->value().toReal()));
}
qDebug() << "Adding line series point:" << (offset + i) << entry->timestamp().toMSecsSinceEpoch() << (entry->value().toReal());
m_graphSeries->append(QPointF(entry->timestamp().toMSecsSinceEpoch(), entry->value().toReal()));
@ -264,7 +264,7 @@ void LogsModelNg::logsReply(const QVariantMap &data)
}
endInsertRows();
emit countChanged();
qDebug() << "min" << m_minValue << "max" << m_maxValue << "newMin" << newMin << "newMax" << newMax;
// qDebug() << "min" << m_minValue << "max" << m_maxValue << "newMin" << newMin << "newMax" << newMax;
if (m_minValue != newMin) {
m_minValue = newMin;
emit minValueChanged();
@ -274,6 +274,9 @@ void LogsModelNg::logsReply(const QVariantMap &data)
emit maxValueChanged();
}
m_busy = false;
emit busyChanged();
if (m_viewStartTime.isValid() && m_list.count() > 0 && m_list.last()->timestamp() > m_viewStartTime && canFetchMore()) {
fetchMore();
}
@ -282,7 +285,7 @@ void LogsModelNg::logsReply(const QVariantMap &data)
void LogsModelNg::fetchMore(const QModelIndex &parent)
{
Q_UNUSED(parent)
qDebug() << "fetchMore called";
// qDebug() << "fetchMore called";
if (!m_engine) {
qWarning() << "Cannot update. Engine not set";
@ -326,13 +329,13 @@ void LogsModelNg::fetchMore(const QModelIndex &parent)
params.insert("offset", m_list.count());
m_engine->jsonRpcClient()->sendCommand("Logging.GetLogEntries", params, this, "logsReply");
qDebug() << "GetLogEntries called";
// qDebug() << "GetLogEntries called";
}
bool LogsModelNg::canFetchMore(const QModelIndex &parent) const
{
Q_UNUSED(parent)
qDebug() << "canFetchMore" << (m_engine && m_canFetchMore);
// qDebug() << "canFetchMore" << (m_engine && m_canFetchMore);
return m_engine && m_canFetchMore;
}
@ -364,6 +367,15 @@ void LogsModelNg::newLogEntryReceived(const QVariantMap &data)
m_list.prepend(entry);
if (m_graphSeries) {
m_graphSeries->insert(0, QPointF(entry->timestamp().toMSecsSinceEpoch(), entry->value().toReal()));
if (m_minValue > entry->value().toReal()) {
m_minValue = entry->value().toReal();
emit minValueChanged();
}
if (m_maxValue < entry->value().toReal()) {
m_maxValue = entry->value().toReal();
emit maxValueChanged();
}
}
endInsertRows();
emit countChanged();

View File

@ -150,5 +150,6 @@
<file>ui/images/weather-app-symbolic.svg</file>
<file>ui/images/zoom-out.svg</file>
<file>ui/images/zoom-in.svg</file>
<file>ui/images/smartmeter.svg</file>
</qresource>
</RCC>

View File

@ -57,6 +57,7 @@
<file>ui/customviews/MediaControllerView.qml</file>
<file>ui/customviews/SensorView.qml</file>
<file>ui/customviews/NotificationsView.qml</file>
<file>ui/customviews/ExtendedVolumeController.qml</file>
<file>ui/devicepages/MediaDevicePage.qml</file>
<file>ui/devicepages/ButtonDevicePage.qml</file>
<file>ui/devicepages/GenericDeviceStateDetailsPage.qml</file>
@ -133,6 +134,6 @@
<file>../LICENSE</file>
<file>ui/customviews/GenericTypeGraphPre110.qml</file>
<file>ui/customviews/GenericTypeGraph.qml</file>
<file>ui/customviews/SensorChart.qml</file>
<file>ui/devicepages/SmartMeterDevicePage.qml</file>
</qresource>
</RCC>

View File

@ -53,7 +53,7 @@ ApplicationWindow {
rootItem.handleCloseEvent(close)
}
property var supportedInterfaces: ["light", "weather", "sensor", "media", "garagegate", "awning", "shutter", "blind", "accesscontrol", "button", "notifications", "inputtrigger", "outputtrigger", "gateway"]
property var supportedInterfaces: ["light", "weather", "sensor", "media", "garagegate", "awning", "shutter", "blind", "smartmeter", "accesscontrol", "button", "notifications", "inputtrigger", "outputtrigger", "gateway"]
function interfaceToString(name) {
switch(name) {
case "light":
@ -93,6 +93,8 @@ ApplicationWindow {
return qsTr("Garage gates");
case "accesscontrol":
return qsTr("Access control");
case "smartmeter":
return qsTr("Smart meter");
case "uncategorized":
return qsTr("Uncategorized")
default:
@ -149,8 +151,6 @@ ApplicationWindow {
return Qt.resolvedUrl("images/network-wired-symbolic.svg")
case "notifications":
return Qt.resolvedUrl("images/notification.svg")
case "connectable":
return Qt.resolvedUrl("images/stock_link.svg")
case "inputtrigger":
return Qt.resolvedUrl("images/attention.svg")
case "outputtrigger":
@ -175,6 +175,14 @@ ApplicationWindow {
return Qt.resolvedUrl("images/fingerprint.svg")
case "accesscontrol":
return Qt.resolvedUrl("images/network-secure.svg");
case "smartmeter":
case "smartmeterconsumer":
case "smartmeterproducer":
case "extendedsmartmeterconsumer":
case "extendedsmartmeterproducer":
return Qt.resolvedUrl("images/smartmeter.svg")
case "connectable":
return Qt.resolvedUrl("images/stock_link.svg")
default:
console.warn("InterfaceToIcon: Unhandled interface", name)
}
@ -187,7 +195,11 @@ ApplicationWindow {
"moisturesensor":"blue",
"lightsensor": "orange",
"conductivitysensor": "green",
"pressuresensor": "grey"
"pressuresensor": "grey",
"smartmeterproducer": "lightgreen",
"smartmeterconsumer": "orange",
"extendedsmartmeterproducer": "blue",
"extendedsmartmeterconsumer": "blue"
}
function interfaceToColor(name) {
@ -244,6 +256,8 @@ ApplicationWindow {
page = "NotificationsDevicePage.qml";
} else if (interfaceList.indexOf("fingerprintreader") >= 0) {
page = "FingerprintReaderDevicePage.qml";
} else if (interfaceList.indexOf("smartmeter") >= 0) {
page = "SmartMeterDevicePage.qml"
} else {
page = "GenericDevicePage.qml";
}

View File

@ -9,10 +9,11 @@ import QtCharts 2.2
Item {
id: root
implicitHeight: width * .6
property var device: null
property var stateType: null
property var valueState: device.states.getState(stateType.id)
readonly property var valueState: device.states.getState(stateType.id)
readonly property var deviceClass: engine.deviceManager.deviceClasses.getDeviceClass(device.deviceClassId);
readonly property bool hasConnectable: deviceClass.interfaces.indexOf("connectable") >= 0
readonly property var connectedStateType: hasConnectable ? deviceClass.stateTypes.findByName("connected") : null
@ -94,8 +95,8 @@ Item {
ValueAxis {
id: yAxis
min: logsModelNg.minValue - logsModelNg.minValue * .05
max: logsModelNg.maxValue + logsModelNg.maxValue * .05
max: logsModelNg.maxValue + Math.abs(logsModelNg.maxValue * .05)
min: logsModelNg.minValue - Math.abs(logsModelNg.minValue * .05)
labelsFont.pixelSize: app.smallFont
labelsColor: app.foregroundColor
tickCount: chartView.height / 40
@ -179,10 +180,14 @@ Item {
min: {
var date = new Date();
date.setHours(date.getHours() - 6);
date.setTime(date.getTime() - (1000 * 60 * 60 * 6) + 2000);
return date;
}
max: {
var date = new Date();
date.setTime(date.getTime() + 2000)
return date;
}
max: new Date()
}
AreaSeries {
@ -209,8 +214,38 @@ Item {
name: root.stateType.displayName
borderColor: root.color
borderWidth: 4
lowerSeries: LineSeries {
id: lineSeries0
XYPoint { x: xAxis.max.getTime(); y: 0 }
XYPoint { x: xAxis.min.getTime(); y: 0 }
}
upperSeries: LineSeries {
id: lineSeries1
onPointAdded: {
var newPoint = lineSeries1.at(index)
if (newPoint.x > lineSeries0.at(0).x) {
lineSeries0.replace(0, newPoint.x, 0)
}
if (newPoint.x < lineSeries0.at(1).x) {
lineSeries0.replace(1, newPoint.x, 0)
}
if (newPoint.x <= xAxis.max.getTime() || logsModelNg.busy) {
return;
}
var diffMaxToNew = newPoint.x - xAxis.max.getTime();
print("diffToNew is", diffMaxToNew)
if (diffMaxToNew < 1000 * 60 * 5) {
chartView.animationOptions = ChartView.NoAnimation
var newMin = xAxis.min.getTime() + diffMaxToNew;
xAxis.max = new Date(newPoint.x);
xAxis.min = new Date(newMin)
chartView.animationOptions = ChartView.SeriesAnimations
}
}
}
color: Qt.rgba(root.color.r, root.color.g, root.color.b, .3)
onHovered: {
@ -268,9 +303,13 @@ Item {
MouseArea {
anchors.fill: parent
x: chartView.plotArea.x
y: chartView.plotArea.y
width: chartView.plotArea.width
height: chartView.plotArea.height
property int lastX: 0
property int lastY: 0
preventStealing: false
function scrollRightLimited(dx) {
chartView.animationOptions = ChartView.NoAnimation
@ -303,7 +342,7 @@ Item {
lastY = mouse.y
}
onClicked: {
var pt = chartView.mapToValue(Qt.point(mouse.x, mouse.y), mainSeries)
var pt = chartView.mapToValue(Qt.point(mouse.x + chartView.plotArea.x, mouse.y + chartView.plotArea.y), mainSeries)
mainSeries.markClosestPoint(pt)
}

View File

@ -1,28 +0,0 @@
import QtQuick 2.5
import QtQuick.Controls 2.1
import QtQuick.Controls.Material 2.1
import QtQuick.Layouts 1.3
import "../components"
import Nymea 1.0
CustomViewBase {
id: root
implicitHeight: width * .6
property string interfaceName
readonly property string stateTypeName: {
switch (interfaceName) {
case "lightsensor":
return "lightIntensity";
default:
return interfaceName.replace("sensor", "");
}
}
GenericTypeGraph {
anchors { left: parent.left; top: parent.top; right: parent.right; bottom: parent.bottom }
device: root.device
stateType: root.deviceClass.stateTypes.findByName(root.stateTypeName)
color: app.interfaceToColor(root.interfaceName)
iconSource: app.interfaceToIcon(root.interfaceName)
}
}

View File

@ -18,10 +18,21 @@ ListView {
}
}
}
delegate: SensorChart {
delegate: GenericTypeGraph {
width: parent.width
interfaceName: modelData
device: root.device
deviceClass: root.deviceClass
stateType: root.deviceClass.stateTypes.findByName(stateTypeName)
color: app.interfaceToColor(modelData)
iconSource: app.interfaceToIcon(modelData)
implicitHeight: width * .6
property string stateTypeName: {
switch (modelData) {
case "lightsensor":
return "lightIntensity";
default:
return modelData.replace("sensor", "");
}
}
}
}

View File

@ -0,0 +1,44 @@
import QtQuick 2.5
import QtQuick.Controls 2.1
import QtQuick.Layouts 1.1
import Nymea 1.0
import "../components"
import "../customviews"
DevicePageBase {
id: root
ListView {
anchors { fill: parent }
model: ListModel {
Component.onCompleted: {
if (root.deviceClass.interfaces.indexOf("extendedsmartmeterproducer") >= 0
|| root.deviceClass.interfaces.indexOf("extendedsmartmeterconsumer") >= 0) {
append( {interface: "extendedsmartmeterproducer", stateTypeName: "currentPower" })
}
if (root.deviceClass.interfaces.indexOf("smartmeterproducer") >= 0) {
append( {interface: "smartmeterproducer", stateTypeName: "totalEnergyProduced" })
}
if (root.deviceClass.interfaces.indexOf("smartmeterconsumer") >= 0) {
append( {interface: "smartmeterconsumer", stateTypeName: "totalEnergyConsumed" })
}
print("shown graphs are", count)
}
}
delegate: ColumnLayout {
width: parent.width
Label {
Layout.fillWidth: true
Layout.leftMargin: app.margins; Layout.topMargin: app.margins; Layout.rightMargin: app.rightMargins;
text: root.deviceClass.stateTypes.findByName(model.stateTypeName).displayName
}
GenericTypeGraph {
Layout.fillWidth: true
device: root.device
stateType: root.deviceClass.stateTypes.findByName(model.stateTypeName)
color: app.interfaceToColor(model.interface)
iconSource: app.interfaceToIcon(model.interface)
}
}
}
}

View File

@ -0,0 +1,202 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 96 96.000001"
version="1.1"
id="svg4874"
height="96"
width="96">
<defs
id="defs4876" />
<metadata
id="metadata4879">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
transform="translate(67.857146,-78.50504)"
id="layer1">
<g
style="display:inline"
id="g4845"
transform="matrix(0,-1,-1,0,373.50506,516.50504)">
<g
id="g4778"
transform="matrix(-0.9996045,0,0,1,575.94296,-611.00001)">
<g
style="display:inline"
id="g4780"
transform="matrix(-1,0,0,1,575.99999,611)">
<path
transform="matrix(0.00413041,-0.62875985,0.62925749,0.0041304,140.84654,636.94341)"
d="m 391.66992,387.37109 c -1.95315,3.8e-4 -3.90597,0.74333 -5.39648,2.23243 l -0.002,0.002 c -2.98084,2.9782 -2.98179,7.80618 -0.002,10.7832 2.701,2.69845 34.10981,26.89007 39.70508,28.92188 0.001,-0.001 0.002,-0.002 0.004,-0.002 0.002,-3.2e-4 0.006,0 0.008,0 0.003,2.3e-4 0.004,4.4e-4 0.006,0 0.002,0 0.005,-0.001 0.006,-0.002 -1.9443,-5.61196 -26.23068,-37.00669 -28.93164,-39.70508 -1.48993,-1.4886 -3.44333,-2.23084 -5.39649,-2.23047 z"
id="path4197"
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4.77025032;marker:none;enable-background:accumulate" />
<rect
transform="scale(-1,1)"
y="345.36221"
x="-438.00244"
height="96"
width="96.037987"
id="rect4782"
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;stroke:none;stroke-width:4;marker:none;enable-background:accumulate" />
<ellipse
ry="40.015827"
rx="40"
transform="matrix(0,-1,-1,0,0,0)"
cy="-389.98346"
cx="-393.36221"
id="path833"
style="fill:none;fill-opacity:1;stroke:#808080;stroke-width:4.00079123;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
width="4"
transform="matrix(0,-1,-1,0,0,0)"
y="-425.51035"
x="-395.36221"
height="8.0031652"
id="rect835"
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:3.78027534;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:3.78027534;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect845"
height="8.0023737"
x="-537.62946"
y="-176.48611"
transform="matrix(-0.50014835,-0.86593974,-0.86611103,0.49985167,0,0)"
width="4.0003958" />
<rect
width="4.0003958"
transform="matrix(0.50014835,-0.86593974,-0.86611103,-0.49985167,0,0)"
y="-569.96503"
x="-147.76164"
height="8.0023737"
id="rect851"
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:3.78027534;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:3.78027534;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect857"
height="8.0007915"
x="138.96216"
y="-571.14557"
transform="matrix(0.86611103,-0.49985167,-0.50014835,-0.86593974,0,0)"
width="4.0011868" />
<rect
width="4.0015826"
transform="scale(1,-1)"
y="-428.87503"
x="387.98267"
height="8"
id="rect863"
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:3.78027534;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:3.78027534;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect869"
height="8.0007915"
x="532.44104"
y="-181.2778"
transform="matrix(0.86611103,0.49985166,0.50014835,-0.86593974,0,0)"
width="4.0011873" />
<rect
width="4.0011868"
transform="matrix(-0.86611103,0.49985167,0.50014835,0.86593974,0,0)"
y="500.11292"
x="-142.96335"
height="8.0007915"
id="rect875"
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:3.78027534;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:3.78027534;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect881"
height="8"
x="-391.98428"
y="357.84937"
transform="scale(-1,1)"
width="4.0015826" />
<rect
width="4.0011873"
transform="matrix(-0.86611103,-0.49985166,-0.50014835,0.86593974,0,0)"
y="110.24509"
x="-536.44226"
height="8.0007915"
id="rect909"
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:3.78027534;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:1.65583444;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect921"
height="4.0002618"
x="-196.96432"
y="482.22253"
transform="matrix(-0.91360523,0.40660236,0.40687094,0.91348565,0,0)"
width="1.5353957" />
<rect
width="1.5354383"
transform="matrix(-0.95109244,0.30890642,0.30912758,0.95102058,0,0)"
y="458.86975"
x="-250.05089"
height="4.0001512"
id="rect935"
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:1.65583444;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:1.65583444;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect941"
height="4.0000682"
x="-300.40701"
y="430.10257"
transform="matrix(-0.97816432,0.20783299,0.2079904,0.97813087,0,0)"
width="1.53547" />
<rect
width="1.5354896"
transform="matrix(-0.99452619,0.10448756,0.10456937,0.9945176,0,0)"
y="396.23618"
x="-347.47949"
height="4.0000172"
id="rect947"
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:1.65583444;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:1.65583444;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect953"
height="4.0000172"
x="-429.74677"
y="314.73932"
transform="matrix(-0.99452619,-0.1044876,-0.10456937,0.9945176,0,0)"
width="1.5354896" />
<rect
width="1.53547"
transform="matrix(-0.97816432,-0.20783303,-0.2079904,0.97813087,0,0)"
y="267.99973"
x="-464.03812"
height="4.0000682"
id="rect959"
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:1.65583444;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:1.65583444;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect965"
height="4.0001512"
x="-493.24908"
y="217.93295"
transform="matrix(-0.95109242,-0.30890647,-0.30912758,0.95102058,0,0)"
width="1.5354382" />
<rect
style="fill:#808080;fill-opacity:1;stroke:none;stroke-width:1.65583444;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect977"
height="4.0002618"
x="-517.05963"
y="165.08612"
transform="matrix(-0.91360522,-0.4066024,-0.40687094,0.91348565,0,0)"
width="1.5353957" />
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.9 KiB