From 8ef48cf95b9dd4886e3f386cb8320bd5b7724e64 Mon Sep 17 00:00:00 2001 From: Miro Eklund Date: Wed, 14 Apr 2021 09:52:57 +0300 Subject: [PATCH] New trend axis mode SingleAxisShowLegends Allows displaying the names of all plotted lines as part of a single axis. related to #693 Change-Id: Iab5be5582a03ae4aaabe2ba1ece0280f23a57c95 --- .../trend/DemoTrendSingleAxisShowLegends.java | 139 ++++++++++++++++++ .../trend/configuration/TrendSpec.java | 2 + .../trend/configuration/YAxisMode.java | 6 +- .../org/simantics/trend/impl/TrendNode.java | 25 +++- .../org/simantics/trend/impl/VertRuler.java | 121 +++++++++++---- 5 files changed, 260 insertions(+), 33 deletions(-) create mode 100644 bundles/org.simantics.trend/example/org/simantics/trend/DemoTrendSingleAxisShowLegends.java diff --git a/bundles/org.simantics.trend/example/org/simantics/trend/DemoTrendSingleAxisShowLegends.java b/bundles/org.simantics.trend/example/org/simantics/trend/DemoTrendSingleAxisShowLegends.java new file mode 100644 index 000000000..bdb22a3f3 --- /dev/null +++ b/bundles/org.simantics.trend/example/org/simantics/trend/DemoTrendSingleAxisShowLegends.java @@ -0,0 +1,139 @@ +/******************************************************************************* + * Copyright (c) 2007, 2011 Association for Decentralized Information Management in + * Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.trend; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; + +import javax.swing.JFrame; + +import org.simantics.g2d.canvas.impl.CanvasContext; +import org.simantics.g2d.chassis.AWTChassis; +import org.simantics.g2d.image.DefaultImages; +import org.simantics.history.impl.FileHistory; +import org.simantics.trend.configuration.Scale; +import org.simantics.trend.configuration.TrendItem; +import org.simantics.trend.configuration.TrendSpec; +import org.simantics.trend.configuration.YAxisMode; +import org.simantics.trend.impl.Milestone; +import org.simantics.trend.impl.MilestoneSpec; +import org.simantics.trend.impl.TrendNode; +import org.simantics.trend.impl.TrendParticipant; +import org.simantics.utils.FileUtils; +import org.simantics.utils.datastructures.hints.IHintContext; +import org.simantics.utils.threads.AWTThread; +import org.simantics.utils.threads.IThreadWorkQueue; + +public class DemoTrendSingleAxisShowLegends { + + public static void main(String[] args) throws Exception { + System.out.println(DefaultImages.HAND); + + // Initialize file history + final File workarea = FileUtils.createTmpDir(); + final TestData data = new TestData(workarea); + FileHistory fh = ((FileHistory) data.historyManager); + fh.asyncUsage = false; + // Memory history + // final TestData data = new TestData(); + + final TrendSpec trendSpec = new TrendSpec(); + trendSpec.init(); + trendSpec.viewProfile.showMilestones = true; + trendSpec.name = "SingleAxisShowLegends Y-Axis DemoTrend"; + trendSpec.axisMode = YAxisMode.SingleAxisShowLegends; + trendSpec.singleAxisShowLegendsMaxLegends = 10; + + trendSpec.items.add(new TrendItem(1, "Sine", data.subscriptionId, "Sine", new Scale.Manual(-100, 100), + TrendItem.Renderer.Analog)); + trendSpec.items.add(new TrendItem(2, "Random", data.subscriptionId, "Random", new Scale.Auto(), TrendItem.Renderer.Analog)); + + trendSpec.viewProfile.profileName = "Profile"; +// trendSpec.viewProfile.timeWindow.timeWindowLength = 180.0; +// trendSpec.viewProfile.timeWindow.timeWindowStart = 0.0; + trendSpec.viewProfile.timeWindow.timeWindowIncrement = 75.0; + + // Alternative spec - Switch with Spacebarman button +// TrendSpec altSpec = new TrendSpec(); +// altSpec.init(); +// altSpec.viewProfile.showMilestones = true; +// altSpec.name = "Single Axis"; +// altSpec.axisMode = YAxisMode.SingleAxis; +// altSpec.items.add( new TrendItem( 1, "Sine", data.subscriptionId,"Sine", new Scale.Auto(), TrendItem.Renderer.Analog, 0, 1 ) ); +// altSpec.items.add( new TrendItem( 2, "Ramp", data.subscriptionId,"Ramp", new Scale.Auto(), TrendItem.Renderer.Binary ) ); +// //altSpec.items.add( new TrendItem( "Random", data.subscriptionId,"Random", new Scale.FitAll(), TrendItem.Renderer.Analog, 0, 1, DrawMode.Deviation ) ); +// altSpec.viewProfile.profileName = "Profile"; +// //altSpec.viewProfile.timeWindow.timeWindowLength = 30.0; +// //altSpec.viewProfile.timeWindow.timeWindowStart = 0.0; +// altSpec.viewProfile.timeWindow.timeWindowIncrement = 25.0; + + // JFrame has double buffering enabled by default + JFrame frame = new JFrame("Demo Trend"); + // Add a window listener for close button + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + data.dispose(); + System.exit(0); + } + }); + + data.solver.start(); + + // This is an empty content area in the frame + final AWTChassis chassis = new AWTChassis(); + + chassis.setPreferredSize(new Dimension(480, 320)); + + frame.getContentPane().add(chassis, BorderLayout.CENTER); + frame.pack(); + + frame.setVisible(true); + chassis.requestFocus(); + + IThreadWorkQueue thread = AWTThread.getThreadAccess(); + + final CanvasContext ctx = TrendInitializer.createDefaultCanvas(thread, data.historyManager, data.collector, + data.solver, trendSpec); + TrendNode node = TrendInitializer.getTrendNode(ctx); + + ctx.getAtMostOneItemOfClass(TrendParticipant.class).setHintAsync(TrendParticipant.KEY_TREND_DRAW_INTERVAL, + 1000L); + + MilestoneSpec milestones = new MilestoneSpec(); + milestones.init(); + Milestone m1 = new Milestone("1", "1", "Event 1", 50); + Milestone m2 = new Milestone("2", "2", "Event 2", 60); + Milestone m3 = new Milestone("3", "3", "Event 3", 80); + Milestone m4 = new Milestone("4", "4", "Event 4", 90); + milestones.milestones.add(m1); + milestones.milestones.add(m2); + milestones.milestones.add(m3); + milestones.milestones.add(m4); + milestones.baseline = 2; + node.setMilestones(milestones); + + @SuppressWarnings("unused") + IHintContext hintCtx = ctx.getDefaultHintContext(); + + thread.asyncExec(new Runnable() { + @Override + public void run() { + chassis.setCanvasContext(ctx); + } + }); + } + +} diff --git a/bundles/org.simantics.trend/src/org/simantics/trend/configuration/TrendSpec.java b/bundles/org.simantics.trend/src/org/simantics/trend/configuration/TrendSpec.java index 9970544d0..46b4a8023 100644 --- a/bundles/org.simantics.trend/src/org/simantics/trend/configuration/TrendSpec.java +++ b/bundles/org.simantics.trend/src/org/simantics/trend/configuration/TrendSpec.java @@ -27,6 +27,8 @@ public class TrendSpec extends Bean { public YAxisMode axisMode; + public int singleAxisShowLegendsMaxLegends = 10; + public ViewProfile viewProfile; /** diff --git a/bundles/org.simantics.trend/src/org/simantics/trend/configuration/YAxisMode.java b/bundles/org.simantics.trend/src/org/simantics/trend/configuration/YAxisMode.java index c3d7e26b2..aad7468b1 100644 --- a/bundles/org.simantics.trend/src/org/simantics/trend/configuration/YAxisMode.java +++ b/bundles/org.simantics.trend/src/org/simantics/trend/configuration/YAxisMode.java @@ -16,7 +16,11 @@ public enum YAxisMode { SingleAxis, // Each variable has its own axis - MultiAxis + MultiAxis, + + // Each variable has the same axis, but legends is shown for each, just like with MultiAxis + // Works only for analog values + SingleAxisShowLegends } diff --git a/bundles/org.simantics.trend/src/org/simantics/trend/impl/TrendNode.java b/bundles/org.simantics.trend/src/org/simantics/trend/impl/TrendNode.java index ddbd4934e..80ffa9767 100644 --- a/bundles/org.simantics.trend/src/org/simantics/trend/impl/TrendNode.java +++ b/bundles/org.simantics.trend/src/org/simantics/trend/impl/TrendNode.java @@ -87,6 +87,7 @@ public class TrendNode extends G2DParentNode implements TrendLayout { public TrendQualitySpec quality = TrendQualitySpec.DEFAULT; public boolean printing = false; boolean singleAxis; + boolean singleAxisShowLegends; // Data nodes List analogItems = new ArrayList(); @@ -322,7 +323,27 @@ public class TrendNode extends G2DParentNode implements TrendLayout { // Setup vertical ruler nodes singleAxis = spec.axisMode == YAxisMode.SingleAxis; - if (singleAxis) { + singleAxisShowLegends = spec.axisMode == YAxisMode.SingleAxisShowLegends; + if(singleAxisShowLegends) { + if (yaxisModeChanged || vertRulers.size() != 1 || vertRuler == null) { + for (VertRuler vr : vertRulers) removeNode(vr); + vertRulers.clear(); + + vertRuler = addNode("VertRuler", VertRuler.class); + vertRulers.add( vertRuler ); + } + + vertRuler.manualscale = true; + vertRuler.singleAxisShowLegendsMaxLegends = spec.singleAxisShowLegendsMaxLegends; + for (int i=0; i extra_labels = new ArrayList<>(); + List extra_label_colors = new ArrayList<>(); + double extra_width = 0.0; + int singleAxisShowLegendsMaxLegends = 10; + static final double TRIANGLE_SIZE = 7; static final Path2D TRIANGLE; @@ -47,7 +53,9 @@ public class VertRuler extends TrendGraphicalNode { ValueFormat vf = trend.valueFormat; spacing = GridSpacing.makeGridSpacing(max-min, getHeight(), 15); labelWidth = Math.max(7, GridUtil.calcLabelWidth(min, max, vf.format, spacing)); + double w = 30 + labelWidth; + // Snap w -> next 20 pixels double quantization = 10; int x = (int) Math.ceil( w / quantization ); @@ -57,13 +65,35 @@ public class VertRuler extends TrendGraphicalNode { bounds.setFrame(0, 0, w, getHeight()); trend.shapedirty = true; } - + + public void addExtraLabel(String label, Color color) { + extra_labels.add(label); + extra_label_colors.add(color); + } + public void setHeight(double height) { if (height==bounds.getHeight()) return; bounds.setFrame(0, 0, bounds.getWidth(), height); getTrend().shapedirty = true; } + @Override + public double getWidth() { + if(extra_labels.size() == 0) { + return super.getWidth(); + } else { + if(extra_width == 0.0) { + extra_width = 15.0 * extra_labels.size(); + double w = bounds.getWidth(); + return w + extra_width; + } else { + double w = bounds.getWidth(); + return w + extra_width; + } + } + + } + public boolean setMinMax(double min, double max) { if (min==this.min && max==this.max) return false; spacing = GridSpacing.makeGridSpacing(max-min, getHeight(), 15); @@ -77,6 +107,9 @@ public class VertRuler extends TrendGraphicalNode { protected void doRender(Graphics2D g) { TrendNode trend = (TrendNode) getParent(); + VertRuler master = trend.vertRuler; + VertRuler slave = this; + // Draw little "Frozen" if ( !trend.printing ) { @@ -88,20 +121,19 @@ public class VertRuler extends TrendGraphicalNode { // Draw at top g.drawString(txt, 5.f, -9.f ); } - + g.setPaint( color ); g.setStroke( GridUtil.RULER_LINE_STROKE ); - + ValueFormat vf = trend.valueFormat; - VertRuler master = trend.vertRuler; - VertRuler slave = this; + if ( master != slave ) { // Paint "slave" ruler - a ruler with ticks from master and labels from this int tickCount = GridUtil.getTickCount(master.spacing, master.min, master.getHeight()); int noOfDecimals = calcNoOfDecimals(tickCount, slave.max-slave.min); Format format = vf.toFormat(noOfDecimals); - + GridUtil.paintVerticalSlaveRuler( master.spacing, spacing, @@ -112,16 +144,16 @@ public class VertRuler extends TrendGraphicalNode { format); } else { Format format = vf.format; - + // Paint normal ruler GridUtil.paintVerticalRuler( - spacing, - g, - min, - getHeight(), - format); + spacing, + g, + min, + getHeight(), + format); } - + // Draw label { // Shape oldClip = g2d.getClip(); @@ -133,25 +165,54 @@ public class VertRuler extends TrendGraphicalNode { Font font = selected ? RULER_FONT_BOLD : RULER_FONT; FontMetrics fm = g.getFontMetrics( font ); //LineMetrics lm = fm.getLineMetrics(label, g); - double wid = fm.stringWidth(label); - AffineTransform at = g.getTransform(); - g.translate( getWidth()-15, (getHeight()-wid)/2); -// g2d.translate( 18+labelWidth, (getHeight()-wid)/2); - g.transform( AffineTransform.getQuadrantRotateInstance(1) ); - g.setColor( color ); - g.setFont( font ); - g.drawString( label, (float) 0, (float) 0); - g.setTransform( at ); -// g2d.setClip(oldClip); - - // Triangle - if (selected) { - at = g.getTransform(); - g.translate( getWidth() - TRIANGLE_SIZE - 5, 0 ); + if(extra_labels.size() == 0) { + double wid = fm.stringWidth(label); + + AffineTransform at = g.getTransform(); + g.translate( getWidth()-15, (getHeight()-wid)/2); + // g2d.translate( 18+labelWidth, (getHeight()-wid)/2); + g.transform( AffineTransform.getQuadrantRotateInstance(1) ); g.setColor( color ); - g.fill( TRIANGLE ); + g.setFont( font ); + g.drawString( label, (float) 0, (float) 0); g.setTransform( at ); + // g2d.setClip(oldClip); + + // Triangle + if (selected) { + at = g.getTransform(); + g.translate( getWidth() - TRIANGLE_SIZE - 5, 0 ); + g.setColor( color ); + g.fill( TRIANGLE ); + g.setTransform( at ); + } + } else { + extra_width = 0.0; + double bounds_width = bounds.getWidth(); + + for(int label_index = 0; label_index < extra_labels.size(); label_index++) { + if(label_index >= singleAxisShowLegendsMaxLegends) { + break; // Maximum amount of labels that we should display has been reached + } + + String label = extra_labels.get(label_index); + Color color = extra_label_colors.get(label_index); + + double wid = fm.stringWidth(label); + double font_height = fm.getHeight(); + extra_width += font_height; + //Letters are displayed in 90 degree angle, so font height is the width of the label as seen in X-direction + + AffineTransform at = g.getTransform(); + g.translate( bounds_width + label_index*font_height, (getHeight()-wid)/2); + // g2d.translate( 18+labelWidth, (getHeight()-wid)/2); + g.transform( AffineTransform.getQuadrantRotateInstance(1) ); + g.setColor( color ); + g.setFont( font ); + g.drawString( label, (float) 0, (float) 0); + g.setTransform( at ); + } } } } -- 2.43.2