diff --git a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/views/SecuritiesChart.java b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/views/SecuritiesChart.java index d263160d35..db6e1060e6 100644 --- a/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/views/SecuritiesChart.java +++ b/name.abuchen.portfolio.ui/src/name/abuchen/portfolio/ui/views/SecuritiesChart.java @@ -40,8 +40,8 @@ import org.swtchart.ICustomPaintListener; import org.swtchart.ILegend; import org.swtchart.ILineSeries; -import org.swtchart.IPlotArea; import org.swtchart.ILineSeries.PlotSymbolType; +import org.swtchart.IPlotArea; import org.swtchart.ISeries; import org.swtchart.ISeries.SeriesType; import org.swtchart.LineStyle; @@ -381,13 +381,13 @@ public SecuritiesChart(Composite parent, Client client, CurrencyConverter conver chart.getPlotArea().addPaintListener(event -> customPaintListeners.forEach(l -> l.paintControl(event))); chart.getPlotArea().addPaintListener(event -> customBehindPaintListener.forEach(l -> l.paintControl(event))); - setupTooltip(); - ILegend legend = chart.getLegend(); legend.setPosition(SWT.BOTTOM); legend.setVisible(true); - new CrosshairPainter(chart); + new MeasureLinePainter(chart); + + setupTooltip(); } public IntervalOption getIntervalOption() @@ -966,10 +966,14 @@ private void addLimitLines(ChartInterval chartInterval, ChartRange range) LimitPrice limitAttribute = (LimitPrice) val; - // unwrap because ReadOnlyClient only contains/provides default - // attributes - Optional attributeName = ReadOnlyClient.unwrap(client).getSettings().getAttributeTypes() - .filter(attr -> attr.getId().equals(key)).findFirst(); + Optional attributeName = ReadOnlyClient.unwrap(client) // unwrap + // because + // ReadOnlyClient + // only + // contains/provides + // default + // attributes + .getSettings().getAttributeTypes().filter(attr -> attr.getId().equals(key)).findFirst(); // could not find name of limit attribute --> don't draw if (attributeName.isEmpty()) return; @@ -1618,13 +1622,13 @@ private Optional getMovingAveragePurchasePrice(Client filteredClient, Cu .map(r -> r.getMovingAverageCostPerSharesHeld().getAmount() / Values.Quote.divider()); } - private class CrosshairPainter implements Listener + private class MeasureLinePainter implements Listener { TimelineChart chart; Point p1; Point p2; - private CrosshairPainter(TimelineChart chart) + private MeasureLinePainter(TimelineChart chart) { this.chart = chart; ((IPlotArea) chart.getPlotArea()).addCustomPaintListener(new ICustomPaintListener() @@ -1632,62 +1636,63 @@ private CrosshairPainter(TimelineChart chart) @Override public void paintControl(PaintEvent e) { - if (p1 == null) + if (p1 == null || p2 == null) return; - - if (p2 == null) - { - if (!redrawOnMove) - drawCrosshair(e, p1.x, p1.y); - - return; - } - + drawMeasureLine(e, p1, p2); } - private void drawCrosshair(PaintEvent e, int mouseX, int mouseY) - { - e.gc.setLineStyle(SWT.LINE_SOLID); - e.gc.setForeground(Colors.ICON_ORANGE); - // draw crosshair - e.gc.drawLine(mouseX, 0, mouseX, e.height); - e.gc.drawLine(0, mouseY, e.width, mouseY); - - // draw x- and y-value - LocalDate date = Instant - .ofEpochMilli((long) chart.getAxisSet().getXAxis(0).getDataCoordinate(mouseX)) - .atZone(ZoneId.systemDefault()).toLocalDate(); - e.gc.drawText(Values.Date.format(date), mouseX + 5, e.height - 20, true); - e.gc.drawText(String.valueOf(chart.getAxisSet().getYAxis(0).getDataCoordinate(mouseY)), - e.width - 80, mouseY + 5, true); - } - private void drawMeasureLine(PaintEvent e, Point p1, Point p2) { + LocalDateTime StartDate; + LocalDateTime EndDate; + double yValP1; + double yValP2; + e.gc.setLineStyle(SWT.LINE_SOLID); e.gc.setForeground(Colors.ICON_ORANGE); + + e.gc.drawLine(p1.x, p1.y, p2.x, p2.y); + e.gc.setForeground(Colors.theme().defaultForeground()); + e.gc.fillOval(p1.x - 2, p1.y - 2, 4, 4); + e.gc.fillOval(p2.x - 2, p2.y - 2, 4, 4); + + if (p1.x < p2.x) + { + yValP1 = getYValue(p1); + yValP2 = getYValue(p2); + + StartDate = Instant.ofEpochMilli((long) chart.getAxisSet().getXAxis(0).getDataCoordinate(p1.x)).atZone(ZoneId.systemDefault()).toLocalDateTime(); + EndDate = Instant.ofEpochMilli((long) chart.getAxisSet().getXAxis(0).getDataCoordinate(p2.x)).atZone(ZoneId.systemDefault()).toLocalDateTime(); + } + else + { + yValP1 = getYValue(p2); + yValP2 = getYValue(p1); + + StartDate = Instant.ofEpochMilli((long) chart.getAxisSet().getXAxis(0).getDataCoordinate(p2.x)).atZone(ZoneId.systemDefault()).toLocalDateTime(); + EndDate = Instant.ofEpochMilli((long) chart.getAxisSet().getXAxis(0).getDataCoordinate(p1.x)).atZone(ZoneId.systemDefault()).toLocalDateTime(); + } - e.gc.drawLine(p1.x, p1.y, p2.x,p2.y); - e.gc.setBackground(Colors.ICON_ORANGE); - e.gc.fillOval(p1.x-2, p1.y-2, 4, 4); - e.gc.fillOval(p2.x-2, p2.y-2, 4, 4); - - double yValP1 = getYValue(p1); - double yValP2 = getYValue(p2); - - String text = "P1: " + yValP1 + System.lineSeparator() - + "P2: " + yValP2 + System.lineSeparator() - + "P2/P1: " + new DecimalFormat("#.##%").format(yValP2/yValP1) + System.lineSeparator() - + "P2/P1-1: " + new DecimalFormat("+#.##%;-#.##%").format(yValP2/yValP1 - 1); - + String text = "P1: " + new DecimalFormat(Values.Quote.pattern()).format(yValP1) + " (" + Values.DateTime.format(StartDate) + ")" // //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ + + System.lineSeparator() // + + "P2: " + new DecimalFormat(Values.Quote.pattern()).format(yValP2) + " (" + Values.DateTime.format(EndDate) + ")" // //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + + System.lineSeparator() // + + "absolute Abweichung: " // //$NON-NLS-1$ + + new DecimalFormat(Values.Quote.pattern()).format(yValP2 - yValP1) + + System.lineSeparator() // + + "relative Abweichung: " // //$NON-NLS-1$ + + new DecimalFormat(Values.Percent.pattern()).format(1 - (yValP1 - yValP2) / yValP2) + + System.lineSeparator() // + + "prozentuale Abweichung: " // //$NON-NLS-1$ + + new DecimalFormat(Values.Percent.pattern()).format((yValP2 - yValP1) / yValP1); + Point txtExtend = e.gc.textExtent(text); - e.gc.setBackground(Colors.SIDEBAR_TEXT); - - e.gc.fillRectangle((p1.x + p2.x) / 2 - 5, (p1.y + p2.y) / 2 - 5, txtExtend.x + 10, txtExtend.y + 10); - e.gc.setForeground(Colors.GRAY); - e.gc.drawRectangle((p1.x + p2.x) / 2 - 5, (p1.y + p2.y) / 2 - 5, txtExtend.x + 10, txtExtend.y + 10); - e.gc.setForeground(Colors.ICON_ORANGE); + + e.gc.fillRectangle((p1.x + p2.x) / 2 - 5, (p1.y + p2.y) / 2 - 5, txtExtend.x + 10, + txtExtend.y + 10); + e.gc.drawRectangle((p1.x + p2.x) / 2 - 5, (p1.y + p2.y) / 2 - 5, txtExtend.x + 10, + txtExtend.y + 10); e.gc.drawText(text, (p1.x + p2.x) / 2, (p1.y + p2.y) / 2); } @@ -1696,7 +1701,7 @@ public boolean drawBehindSeries() { return false; } - + private double getYValue(Point p) { return chart.getAxisSet().getYAxis(0).getDataCoordinate(p.y); @@ -1715,38 +1720,38 @@ public void handleEvent(Event e) { switch (e.type) { - case SWT.MouseMove: - if (redrawOnMove) - { - p2 = new Point(e.x, e.y); - chart.redraw(); - } - - return; case SWT.MouseDown: - if (e.button==1) + if ((e.stateMask & SWT.SHIFT) != 0 && e.button == 1) { p2 = null; p1 = new Point(e.x, e.y); redrawOnMove = true; chart.redraw(); } - - // delete/erase crosshair on right mouse click - if (e.button == 3) + + return; + case SWT.MouseMove: + if (redrawOnMove) { - p1 = null; - p2 = null; + p2 = new Point(e.x, e.y); chart.redraw(); } - + return; case SWT.MouseUp: - if (e.button == 1) + if (e.button == 1 && !redrawOnMove) + { + p1 = null; + p2 = null; + chart.redraw(); + } + + if ((e.stateMask & SWT.SHIFT) == 0 && e.button == 1) { redrawOnMove = false; chart.redraw(); } + return; default: return;