/** * Returns an {@link IntervalsList} built from a set of {@link Timestamp} using the following * algorithm: if the {@link TimeDuration} between two {@link Timestamp} is inferior to the defined * tolerance, they are grouped in the same {@link TimeInterval}. A {@link TimeInterval} duration * can not be inferior to the defined tolerance. TODO: better algorithm ? */ public IntervalsList toIntervalsList() { IntervalsList ilist = new IntervalsList(); if (isEmpty()) // empty list return ilist; Iterator<Timestamp> iterator = timestamps.iterator(); Timestamp first = iterator.next(); Timestamp previous = first; while (iterator.hasNext()) { Timestamp next = iterator.next(); if (previous.plus(tolerance).compareTo(next) < 0) { if (first.equals(previous)) { ilist.addToSelf(intervalOfSinglePoint(first)); } else { ilist.addToSelf(TimeInterval.between(first, previous)); } first = next; } previous = next; } // Process end of set if (first.equals(previous)) { ilist.addToSelf(intervalOfSinglePoint(first)); } else { ilist.addToSelf(TimeInterval.between(first, previous)); } return ilist; }
/** * Tests {@code ApplianceArchiveReader#getRawValues(int, String, Timestamp, Timestamp)} method for * a float waveform type pv. * * @throws Exception */ @Test public void testDataRetrievalFloat() throws Exception { Timestamp end = Timestamp.now(); Timestamp start = end.minus(TimeDuration.ofHours(24.0)); ArchiveVNumberArray[] vals = getValuesNumberArray("test_pv_wave_float", false, 0, start, end); assertEquals( "Number of values comparison", TestGenMsgIteratorRaw.MESSAGE_LIST_LENGTH, vals.length); ArchiveVNumberArray val = null; for (int i = 0; i < vals.length; i++) { val = vals[i]; double[] array = new double[val.getData().size()]; for (int j = 0; j < array.length; j++) { array[j] = val.getData().getFloat(j); } assertArrayEquals( "Value comparison", TestGenMsgIteratorWaveform.VALUE_FLOAT[i], array, 0.000001); assertEquals( "Timestamp comparison", TimestampHelper.toMillisecs(start) + i, TimestampHelper.toMillisecs(val.getTimestamp())); assertEquals( "Severity", getSeverity(TestGenMsgIteratorRaw.SEVERITIES[i]), val.getAlarmSeverity()); assertEquals("Status", String.valueOf(TestGenMsgIteratorRaw.STATUS[i]), val.getAlarmName()); } }
public void update(final VType value) { display_info = Optional.ofNullable(ValueUtil.displayOf(value)); if (display_info.isPresent()) value_dbl = ValueUtil.numericValueOf(value).doubleValue(); if (value instanceof VNumber) value_str = Double.toString(((VNumber) value).getValue().doubleValue()); else if (value instanceof VEnum) { final VEnum ev = (VEnum) value; if (ev.getIndex() >= 0 && ev.getIndex() < ev.getLabels().size()) value_str = ev.getLabels().get(ev.getIndex()); else value_str = Integer.toString(ev.getIndex()); } else if (value instanceof VString) value_str = ((VString) value).getValue(); else value_str = Objects.toString(value); final Time vtime = ValueUtil.timeOf(value); if (vtime == null) return; final Timestamp stamp = vtime.getTimestamp(); final Instant new_time = Instant.ofEpochSecond(stamp.getSec(), stamp.getNanoSec()); if (time != null) { final Duration duration = Duration.between(time, new_time); final double period = duration.getSeconds() + duration.getNano() * 1e-9; value_period.add(period); } else value_period.reset(); time = new_time; }
/** * Tests {@code ApplianceArchiveReader#getRawValues(int, String, Timestamp, Timestamp)} method for * an enum waveform type pv. * * @throws Exception */ @Test public void testDataRetrievalEnum() throws Exception { // this doesn't seem to be supported on the IOC side Timestamp end = Timestamp.now(); Timestamp start = end.minus(TimeDuration.ofHours(24.0)); try { getValuesEnumArray("test_pv_wave_enum", false, 0, start, end); fail(); } catch (UnsupportedOperationException e) { assertNotNull(e); } }
/** * Tests {@code ApplianceArchiveReader#getRawValues(int, String, Timestamp, Timestamp)} method for * a string wavefdorm type pv. * * @throws Exception */ @Test public void testDataRetrievalString() throws Exception { // this is not supported on the CSS core side Timestamp end = Timestamp.now(); Timestamp start = end.minus(TimeDuration.ofHours(24.0)); try { getValuesStringArray("test_pv_wave_string", false, 0, start, end); fail(); } catch (UnsupportedOperationException e) { assertNotNull(e); } }
public static void main(String[] args) throws Exception { List<String> signals = new ArrayList<>(); Timestamp start = null; Timestamp end = null; String filename = null; for (int i = 0; i < args.length; i++) { String arg = args[i]; if (arg.equals("-s")) { i++; start = format.parse(args[i]); } else if (arg.equals("-e")) { i++; end = format.parse(args[i]); } else if (arg.equals("-o")) { i++; filename = args[i]; } else { signals.add(arg); } } List<TimeSeries> data = RrdToolDB.fetchData(signals, start, end); Map<String, TimeSeries> series = new HashMap<>(); for (int i = 0; i < signals.size(); i++) { series.put(signals.get(i), data.get(i)); } TimeSeriesMulti correlated = TimeSeriesMulti.synchronizeSeries(series); final Point3DWithLabelDataset dataset = Point3DWithLabelDatasets.build( correlated.getValues().get(signals.get(0)), correlated.getValues().get(signals.get(1)), correlated.getValues().get(signals.get(2)), Collections.nCopies(correlated.getValues().get(signals.get(0)).size(), "label")); BubbleUtil.createBubblePlot(filename, dataset, "DATASETLABEL", "", "", "", "", Timestamp.now()); }
@Test public void parse1() { BufferedReader reader = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream("rrdtool1.out"))); RrdToolOutputParser instance = new RrdToolOutputParser(); TimeSeriesMulti result = instance.parse(reader); assertThat(result.getTime().size(), equalTo(241)); assertThat(result.getValues().size(), equalTo(1)); ListDouble values = result.getValues().get("load_1min"); assertThat(values.size(), equalTo(241)); assertThat(result.getTime().get(0), equalTo(Timestamp.of(1349877960, 0))); assertThat(values.getDouble(0), equalTo(Double.NaN)); assertThat(result.getTime().get(150), equalTo(Timestamp.of(1349931960, 0))); assertThat(values.getDouble(150), equalTo(1.1737083333e+00)); assertThat(result.getTime().get(240), equalTo(Timestamp.of(1349964360, 0))); assertThat(values.getDouble(240), equalTo(Double.NaN)); }
/** * @param start_time Start and .. * @param end_time end time of the range to display */ public void setTimerange(final Timestamp start_time, final Timestamp end_time) { final double new_span = end_time.durationFrom(start_time).toSeconds(); if (new_span > 0) { synchronized (this) { this.end_time = end_time; time_span = new_span; } } // Notify listeners for (ModelListener listener : listeners) listener.changedTimerange(); }
@Test public void parse2() { BufferedReader reader = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream("rrdtool2.out"))); RrdToolOutputParser instance = new RrdToolOutputParser(); TimeSeriesMulti result = instance.parse(reader); assertThat(result.getTime().size(), equalTo(241)); assertThat(result.getValues().size(), equalTo(18)); assertThat( result.getValues().keySet(), equalTo( (Set<String>) new HashSet<String>( Arrays.asList( "Setpoint", "Fan1cfm", "Temp1", "Temp3", "Fan2Rpm", "TempOvrSet", "Fan2cfm", "Totalcfm", "Fan1Status", "Temp2", "Fan1Rpm", "Fan2Status", "Fan3cfm", "Fan3Rpm", "Fan3Status", "Fan4cfm", "Fan4Rpm", "Fan4Status")))); ListDouble values = result.getValues().get("Temp1"); assertThat(values.size(), equalTo(241)); assertThat(result.getTime().get(0), equalTo(Timestamp.of(1355416920, 0))); assertThat(values.getDouble(0), equalTo(9.2500000000e+01)); assertThat(result.getTime().get(150), equalTo(Timestamp.of(1355470920, 0))); assertThat(values.getDouble(150), equalTo(9.1000000000e+01)); assertThat(result.getTime().get(240), equalTo(Timestamp.of(1355503320, 0))); assertThat(values.getDouble(240), equalTo(Double.NaN)); }
/** * Given the time and first element of the sample, see if there are more array elements. * * @param stamp Time stamp of the sample * @param dbl0 Value of the first (maybe only) array element * @param severity Severity of the sample * @return Array with given element and maybe more. * @throws Exception on error, including 'cancel' */ private double[] readArrayElements( final Timestamp stamp, final double dbl0, final AlarmSeverity severity) throws Exception { // For performance reasons, only look for array data until we hit a scalar sample. if (is_an_array == false) return new double[] {dbl0}; // See if there are more array elements if (sel_array_samples == null) { // Lazy initialization sel_array_samples = reader.getRDB().getConnection().prepareStatement(reader.getSQL().sample_sel_array_vals); } sel_array_samples.setInt(1, channel_id); sel_array_samples.setTimestamp(2, TimestampHelper.toSQLTimestamp(stamp)); // MySQL keeps nanoseconds in designated column, not TIMESTAMP if (!reader.isOracle()) sel_array_samples.setInt(3, stamp.getNanoSec()); // Assemble array of unknown size in ArrayList .... final ArrayList<Double> vals = new ArrayList<Double>(); reader.addForCancellation(sel_array_samples); try { final ResultSet res = sel_array_samples.executeQuery(); vals.add(new Double(dbl0)); while (res.next()) vals.add(res.getDouble(1)); res.close(); } finally { reader.removeFromCancellation(sel_array_samples); } // Convert to plain double array final int N = vals.size(); final double ret[] = new double[N]; for (int i = 0; i < N; i++) ret[i] = vals.get(i).doubleValue(); // Check if it's in fact just a scalar, and a valid one if (N == 1 && severity != AlarmSeverity.UNDEFINED) { // Found a perfect non-array sample: // Assume that the data is scalar, skip the array check from now on is_an_array = false; } return ret; }
/** * Creates a sine shaped signal between min and max, updating every interval seconds with * samplesPerCycles samples every full sine cycle. * * @param min minimum value * @param max maximum value * @param samplesPerCycle number of samples for each full cycle (each 2 Pi) * @param secondsBeetwenSamples interval between samples in seconds */ public Sine(Double min, Double max, Double samplesPerCycle, Double secondsBeetwenSamples) { super(secondsBeetwenSamples, VDouble.class); this.min = min; this.max = max; this.currentValue = 0; this.samplesPerCycle = samplesPerCycle; range = this.max - this.min; lastValue = newVDouble( 0.0, alarmNone(), newTime(Timestamp.now()), newDisplay( min, min + range * 0.1, min + range * 0.2, "x", Constants.DOUBLE_FORMAT, min + range * 0.8, min + range * 0.9, max, min, max)); }
// Generate a TimeInterval from a single timestamp private TimeInterval intervalOfSinglePoint(Timestamp first) { return TimeInterval.between(first, first.plus(tolerance)); }
/** * @return End time of the data range * @see #isScrollEnabled() */ public synchronized Timestamp getEndTime() { if (scroll_enabled) end_time = Timestamp.now(); return end_time; }
/** * Data Browser model * * <p>Maintains a list of {@link ModelItem}s * * @author Kay Kasemir * @author Takashi Nakamoto changed the model to accept multiple items with the same name so that * Data Browser can show the trend of the same PV in different axes or with different waveform * indexes. */ @SuppressWarnings("nls") public class Model { /** * File extension for data browser config files. plugin.xml registers the editor for this file * extension */ public static final String FILE_EXTENSION = "plt"; // $NON-NLS-1$ /** Previously used file extension */ public static final String FILE_EXTENSION_OLD = "css-plt"; // $NON-NLS-1$ // XML file tags public static final String TAG_DATABROWSER = "databrowser"; public static final String TAG_SCROLL = "scroll"; public static final String TAG_UPDATE_PERIOD = "update_period"; public static final String TAG_LIVE_SAMPLE_BUFFER_SIZE = "ring_size"; public static final String TAG_PVLIST = "pvlist"; public static final String TAG_PV = "pv"; public static final String TAG_NAME = "name"; public static final String TAG_DISPLAYNAME = "display_name"; public static final String TAG_FORMULA = "formula"; public static final String TAG_AXES = "axes"; public static final String TAG_AXIS = "axis"; public static final String TAG_LINEWIDTH = "linewidth"; public static final String TAG_COLOR = "color"; public static final String TAG_RED = "red"; public static final String TAG_GREEN = "green"; public static final String TAG_BLUE = "blue"; public static final String TAG_TRACE_TYPE = "trace_type"; public static final String TAG_SCAN_PERIOD = "period"; public static final String TAG_INPUT = "input"; public static final String TAG_ARCHIVE = "archive"; public static final String TAG_URL = "url"; public static final String TAG_KEY = "key"; public static final String TAG_START = "start"; public static final String TAG_END = "end"; public static final String TAG_LOG_SCALE = "log_scale"; public static final String TAG_AUTO_SCALE = "autoscale"; public static final String TAG_MAX = "max"; public static final String TAG_MIN = "min"; public static final String TAG_BACKGROUND = "background"; public static final String TAG_ARCHIVE_RESCALE = "archive_rescale"; public static final String TAG_REQUEST = "request"; public static final String TAG_VISIBLE = "visible"; public static final String TAG_ANNOTATIONS = "annotations"; public static final String TAG_ANNOTATION = "annotation"; public static final String TAG_ANNOTATION_CURSOR_LINE_STYLE = "line_style"; public static final String TAG_ANNOTATION_SHOW_NAME = "show_name"; public static final String TAG_ANNOTATION_SHOW_POSITION = "show_position"; public static final String TAG_ANNOTATION_COLOR = "color"; public static final String TAG_ANNOTATION_FONT = "font"; public static final String TAG_TIME = "time"; public static final String TAG_VALUE = "value"; public static final String TAG_WAVEFORM_INDEX = "waveform_index"; /** * AJOUT XYGraphMemento * * @author L.PHILIPPE GANIL */ public static final String TAG_TITLE = "title"; public static final String TAG_TITLE_TEXT = "text"; public static final String TAG_TITLE_COLOR = "color"; public static final String TAG_TITLE_FONT = "font"; public static final String TAG_FONT = "font"; public static final String TAG_SCALE_FONT = "scale_font"; public static final String TAG_TIME_AXIS = "time_axis"; // GRID LINE public static final String TAG_GRID_LINE = "grid_line"; public static final String TAG_SHOW_GRID_LINE = "show_grid_line"; public static final String TAG_DASH_GRID_LINE = "dash_grid_line"; // FORMAT public static final String TAG_FORMAT = "format"; public static final String TAG_AUTO_FORMAT = "auto_format"; public static final String TAG_TIME_FORMAT = "time_format"; public static final String TAG_FORMAT_PATTERN = "format_pattern"; public static final String TAG_GRAPH_SETTINGS = "graph_settings"; public static final String TAG_SHOW_TITLE = "show_title"; public static final String TAG_SHOW_LEGEND = "show_legend"; public static final String TAG_SHOW_PLOT_AREA_BORDER = "show_plot_area_border"; public static final String TAG_TRANSPARENT = "transparent"; /** * Default colors for newly added item, used over when reaching the end. * * <p>Very hard to find a long list of distinct colors. This list is definitely too short... */ private static final RGB[] default_colors = { new RGB(21, 21, 196), // blue new RGB(242, 26, 26), // red new RGB(33, 179, 33), // green new RGB(0, 0, 0), // black new RGB(128, 0, 255), // violett new RGB(255, 170, 0), // (darkish) yellow new RGB(255, 0, 240), // pink new RGB(243, 132, 132), // peachy new RGB(0, 255, 11), // neon green new RGB(0, 214, 255), // neon blue new RGB(114, 40, 3), // brown new RGB(219, 128, 4), // orange }; /** Macros */ private IMacroTableProvider macros = null; /** Listeners to model changes */ private final ArrayList<ModelListener> listeners = new ArrayList<ModelListener>(); /** Axes configurations */ private final ArrayList<AxisConfig> axes = new ArrayList<AxisConfig>(); /** * Time Axes configurations Ignore MIN-MAX part because the range is set by start & end properties */ private AxisConfig timeAxis; public AxisConfig getTimeAxis() { return timeAxis; } /** All the items in this model */ private final ArrayList<ModelItem> items = new ArrayList<ModelItem>(); /** * 'run' flag * * @see #start() * @see #stop() */ private boolean is_running = false; /** Period in seconds for scrolling or refreshing */ private double update_period = Preferences.getUpdatePeriod(); /** Timer used to scan PVItems */ private final Timer scanner = new Timer("ScanTimer", true); /** <code>true</code> if scrolling is enabled */ private boolean scroll_enabled = true; /** Time span of data in seconds */ private double time_span = Preferences.getTimeSpan(); /** End time of the data range */ private Timestamp end_time = Timestamp.now(); /** Background color */ private RGB background = new RGB(255, 255, 255); /** Annotations */ private AnnotationInfo[] annotations = new AnnotationInfo[0]; /** How should plot rescale when archived data arrives? */ private ArchiveRescale archive_rescale = Preferences.getArchiveRescale(); /** * Manage XYGraph Configuration Settings * * @author L.PHILIPPE GANIL */ private XYGraphSettings graphSettings = new XYGraphSettings(); public XYGraphSettings getGraphSettings() { return graphSettings; } public void setGraphSettings(XYGraphSettings xYGraphMem) { graphSettings = xYGraphMem; // fireXYGraphMemChanged(settings); } public void fireGraphConfigChanged() { for (ModelListener listener : listeners) listener.changedXYGraphConfig(); } /** @param macros Macros to use in this model */ public void setMacros(final IMacroTableProvider macros) { this.macros = macros; } /** * Resolve macros * * @param text Text that might contain "$(macro)" * @return Text with all macros replaced by their value */ public String resolveMacros(final String text) { if (macros == null) return text; try { return MacroUtil.replaceMacros(text, macros); } catch (InfiniteLoopException ex) { Activator.getLogger() .log(Level.WARNING, "Problem in macro {0}: {1}", new Object[] {text, ex.getMessage()}); return "Macro Error"; } } /** @param listener New listener to notify */ public void addListener(final ModelListener listener) { listeners.add(listener); } /** @param listener Listener to remove */ public void removeListener(final ModelListener listener) { listeners.remove(listener); } /** @return Number of axes in model */ public int getAxisCount() { return axes.size(); } /** * @param axis_index Index of axis, 0 ... <code>getAxisCount()-1</code> * @return {@link AxisConfig} */ public AxisConfig getAxis(final int axis_index) { return axes.get(axis_index); } /** * Return the AxisConfig with the specifc name or null * * @param axis_index Index of axis, 0 ... <code>getAxisCount()-1</code> * @return {@link AxisConfig} */ public AxisConfig getAxis(final String name) { for (AxisConfig axis : axes) { // System.err.println(axis.getName() + " == " + name + "=" + (axis.getName().equals(name))); if (axis.getName().equals(name)) return axis; } return null; } /** * Locate index of value axis * * @param axis Value axis configuration * @return Index of axis (0, ...) or -1 if not in Model */ public int getAxisIndex(final AxisConfig axis) { return axes.indexOf(axis); } /** * @param axis Axis to test * @return First ModelItem that uses the axis, <code>null</code> if axis is empty */ public ModelItem getFirstItemOnAxis(final AxisConfig axis) { for (ModelItem item : items) if (item.getAxis() == axis) return item; return null; } /** * @param axis Axis to test * @return ModelItem linked to this axis count */ public int countActiveItemsOnAxis(final AxisConfig axis) { int count = 0; for (ModelItem item : items) if (item.getAxis() == axis && item.isVisible()) count++; return count; } /** @return First unused axis (no items on axis), <code>null</code> if none found */ public AxisConfig getEmptyAxis() { for (AxisConfig axis : axes) if (getFirstItemOnAxis(axis) == null) return axis; return null; } /** * Add value axis with default settings * * @return Newly added axis configuration */ public AxisConfig addAxis(String name) { if (name == null) name = NLS.bind(Messages.Plot_ValueAxisNameFMT, getAxisCount() + 1); final AxisConfig axis = new AxisConfig(name); axis.setColor(getNextItemColor()); addAxis(axis); return axis; } /** @param axis New axis to add */ public void addAxis(final AxisConfig axis) { axes.add(axis); axis.setModel(this); fireAxisChangedEvent(null); } /** * Add axis at given index. Adding at '1' means the new axis will be at index '1', and what used * to be at '1' will be at '2' and so on. * * @param index Index where axis will be placed. * @param axis New axis to add */ public void addAxis(final int index, final AxisConfig axis) { axes.add(index, axis); axis.setModel(this); fireAxisChangedEvent(null); } /** * @param axis Axis to remove * @throws Error when axis not in model, or axis in use by model item */ public void removeAxis(final AxisConfig axis) { if (!axes.contains(axis)) throw new Error("Unknown AxisConfig"); for (ModelItem item : items) if (item.getAxis() == axis) throw new Error("Cannot removed AxisConfig while in use"); axis.setModel(null); axes.remove(axis); fireAxisChangedEvent(null); } /** @return How should plot rescale after archived data arrived? */ public ArchiveRescale getArchiveRescale() { return archive_rescale; } /** @param archive_rescale How should plot rescale after archived data arrived? */ public void setArchiveRescale(final ArchiveRescale archive_rescale) { if (this.archive_rescale == archive_rescale) return; this.archive_rescale = archive_rescale; for (ModelListener listener : listeners) listener.changedArchiveRescale(); } /** @return {@link ModelItem} count in model */ public int getItemCount() { return items.size(); } /** * Get one {@link ModelItem} * * @param i 0... getItemCount()-1 * @return {@link ModelItem} */ public ModelItem getItem(final int i) { return items.get(i); } /** * Locate item by name. If different items with the same exist in this model, the first occurrence * will be returned. If no item is found with the given name, <code>null</code> will be returned. * Now that this model may have different items with the same name, this method is not recommended * to locate an item. This method just returns an item which just happens to have the given name. * Use {@link #indexOf(ModelItem)} or {@link #getItem(int)} to locate an item in this model. * * @param name * @return ModelItem by that name or <code>null</code> */ public ModelItem getItem(final String name) { for (ModelItem item : items) if (item.getName().equals(name)) return item; return null; } /** * Returns the index of the specified item, or -1 if this list does not contain the item. * * @param item * @return ModelItem */ public int indexOf(final ModelItem item) { return items.indexOf(item); } /** * Called by items to set their initial color * * @return 'Next' suggested item color */ private RGB getNextItemColor() { return default_colors[items.size() % default_colors.length]; } /** * Add item to the model. * * <p>If the item has no color, this will define its color based on the model's next available * color. * * <p>If the model is already 'running', the item will be 'start'ed. * * @param item {@link ModelItem} to add * @throws RuntimeException if item is already in model * @throws Exception on error trying to start a PV Item that's added to a running model */ public void addItem(final ModelItem item) throws Exception { // A new item with the same PV name are allowed to be added in the // model. This way Data Browser can show the trend of the same PV // in different axes or with different waveform indexes. For example, // one may want to show the first element of epics://aaa:bbb in axis 1 // while showing the third element of the same PV in axis 2 to compare // their trends in one chart. // // if (getItem(item.getName()) != null) // throw new RuntimeException("Item " + item.getName() + " already in Model"); // But, if exactly the same instance of the given ModelItem already exists in this // model, it will not be added. if (items.indexOf(item) != -1) throw new RuntimeException("Item " + item.getName() + " already in Model"); // Assign default color if (item.getColor() == null) item.setColor(getNextItemColor()); // Force item to be on an axis if (item.getAxis() == null) { if (axes.size() == 0) addAxis(item.getDisplayName()); item.setAxis(axes.get(0)); } // Check item axis if (!axes.contains(item.getAxis())) throw new Exception("Item " + item.getName() + " added with invalid axis " + item.getAxis()); // Add to model items.add(item); item.setModel(this); if (is_running && item instanceof PVItem) ((PVItem) item).start(scanner); // Notify listeners of new item for (ModelListener listener : listeners) listener.itemAdded(item); } /** * Remove item from the model. * * <p>If the model and thus item are 'running', the item will be 'stopped'. * * @param item * @throws RuntimeException if item is already in model */ public void removeItem(final ModelItem item) { if (is_running && item instanceof PVItem) { final PVItem pv = (PVItem) item; pv.stop(); // Delete its samples: // For one, so save memory. // Also, in case item is later added back in, its old samples // will have gaps because the item was stopped pv.getSamples().clear(); } if (!items.remove(item)) throw new RuntimeException("Unknown item " + item.getName()); // Detach item from model item.setModel(null); // Notify listeners of removed item for (ModelListener listener : listeners) listener.itemRemoved(item); // Remove axis if unused AxisConfig axis = item.getAxis(); item.setAxis(null); if (countActiveItemsOnAxis(axis) == 0) { removeAxis(axis); fireAxisChangedEvent(null); } } /** @return Period in seconds for scrolling or refreshing */ public double getUpdatePeriod() { return update_period; } /** @param period_secs New update period in seconds */ public void setUpdatePeriod(final double period_secs) { // Don't allow updates faster than 10Hz (0.1 seconds) if (period_secs < 0.1) update_period = 0.1; else update_period = period_secs; // Notify listeners for (ModelListener listener : listeners) listener.changedUpdatePeriod(); } /** * The model supports two types of start/end time handling: * * <ol> * <li>Scroll mode: While <code>isScrollEnabled=true</code>, the end time is supposed to be * 'now' and the start time is supposed to be <code>getTimespan()</code> seconds before * 'now'. * <li>Fixed start/end time: While <code>isScrollEnabled=false</code>, the methods <code> * getStartTime()</code>, <code>getEndTime</code> return a fixed start/end time. * </ol> * * @return <code>true</code> if scrolling is enabled */ public synchronized boolean isScrollEnabled() { return scroll_enabled; } /** @param scroll_enabled Should scrolling be enabled? */ public void enableScrolling(final boolean scroll_enabled) { synchronized (this) { if (this.scroll_enabled == scroll_enabled) return; this.scroll_enabled = scroll_enabled; } // Notify listeners for (ModelListener listener : listeners) listener.scrollEnabled(scroll_enabled); } /** * @return time span of data in seconds * @see #isScrollEnabled() */ public synchronized double getTimespan() { return time_span; } /** * @param start_time Start and .. * @param end_time end time of the range to display */ public void setTimerange(final Timestamp start_time, final Timestamp end_time) { final double new_span = end_time.durationFrom(start_time).toSeconds(); if (new_span > 0) { synchronized (this) { this.end_time = end_time; time_span = new_span; } } // Notify listeners for (ModelListener listener : listeners) listener.changedTimerange(); } /** * @param time_span time span of data in seconds * @see #isScrollEnabled() */ public void setTimespan(final double time_span) { if (time_span > 0) { synchronized (this) { this.time_span = time_span; } } // Notify listeners for (ModelListener listener : listeners) listener.changedTimerange(); } /** * @return Start time of the data range * @see #isScrollEnabled() */ public synchronized Timestamp getStartTime() { return getEndTime().minus(TimeDuration.ofSeconds(time_span)); } /** * @return End time of the data range * @see #isScrollEnabled() */ public synchronized Timestamp getEndTime() { if (scroll_enabled) end_time = Timestamp.now(); return end_time; } /** * @return String representation of start time. While scrolling, this is a relative time, * otherwise an absolute date/time. */ public synchronized String getStartSpecification() { if (scroll_enabled) return new RelativeTime(-time_span).toString(); else return TimestampHelper.format(getStartTime()); } /** * @return String representation of end time. While scrolling, this is a relative time, otherwise * an absolute date/time. */ public synchronized String getEndSpecification() { if (scroll_enabled) return RelativeTime.NOW; else return TimestampHelper.format(end_time); } /** @return Background color */ public RGB getPlotBackground() { return background; } /** @param rgb New background color */ public void setPlotBackground(final RGB rgb) { if (background.equals(rgb)) return; background = rgb; // Notify listeners System.out.println("**** Model.setPlotBackground() ****"); for (ModelListener listener : listeners) listener.changedColors(); } /** @param annotations Annotations to keep in model */ public void setAnnotations(final AnnotationInfo[] annotations) { setAnnotations(annotations, true); } public void setAnnotations(final AnnotationInfo[] annotations, final boolean fireChanged) { this.annotations = annotations; if (fireChanged) fireAnnotationsChanged(); } protected void fireAnnotationsChanged() { for (ModelListener listener : listeners) listener.changedAnnotations(); } /** @return Annotation infos of model */ public AnnotationInfo[] getAnnotations() { return annotations; } /** * Start all items: Connect PVs, initiate scanning, ... * * @throws Exception on error */ public void start() throws Exception { if (is_running) throw new RuntimeException("Model already started"); for (ModelItem item : items) { if (!(item instanceof PVItem)) continue; final PVItem pv_item = (PVItem) item; pv_item.start(scanner); } is_running = true; } /** Stop all items: Disconnect PVs, ... */ public void stop() { if (!is_running) throw new RuntimeException("Model wasn't started"); is_running = false; for (ModelItem item : items) { if (!(item instanceof PVItem)) continue; final PVItem pv_item = (PVItem) item; pv_item.stop(); ImportArchiveReaderFactory.removeCachedArchives(pv_item.getArchiveDataSources()); } } /** * Test if any ModelItems received new samples, if formulas need to be re-computed, since the last * time this method was called. * * @return <code>true</code> if there were new samples */ public boolean updateItemsAndCheckForNewSamples() { boolean anything_new = false; // Update any formulas for (ModelItem item : items) { if (item instanceof FormulaItem && ((FormulaItem) item).reevaluate()) anything_new = true; } // Check and reset PV Items for (ModelItem item : items) { if (item instanceof PVItem && item.getSamples().testAndClearNewSamplesFlag()) anything_new = true; } return anything_new; } /** * Notify listeners of changed axis configuration * * @param axis Axis that changed */ public void fireAxisChangedEvent(final AxisConfig axis) { for (ModelListener listener : listeners) listener.changedAxis(axis); } /** * Notify listeners of changed item visibility * * @param item Item that changed */ void fireItemVisibilityChanged(final ModelItem item) { for (ModelListener listener : listeners) listener.changedItemVisibility(item); } /** * Notify listeners of changed item configuration * * @param item Item that changed */ void fireItemLookChanged(final ModelItem item) { for (ModelListener listener : listeners) listener.changedItemLook(item); } /** * Notify listeners of changed item configuration * * @param item Item that changed */ void fireItemDataConfigChanged(final PVItem item) { for (ModelListener listener : listeners) listener.changedItemDataConfig(item); } /** * Find a formula that uses a model item as an input. * * @param item Item that's potentially used in a formula * @return First Formula found that uses this item, or <code>null</code> if none found */ public FormulaItem getFormulaWithInput(final ModelItem item) { // Update any formulas for (ModelItem i : items) { if (!(i instanceof FormulaItem)) continue; final FormulaItem formula = (FormulaItem) i; if (formula.usesInput(item)) return formula; } return null; } /** * Write RGB color to XML document * * @param writer * @param level Indentation level * @param tag_name * @param color */ static void writeColor( final PrintWriter writer, final int level, final String tag_name, final RGB color) { XMLWriter.start(writer, level, tag_name); writer.println(); XMLWriter.XML(writer, level + 1, Model.TAG_RED, color.red); XMLWriter.XML(writer, level + 1, Model.TAG_GREEN, color.green); XMLWriter.XML(writer, level + 1, Model.TAG_BLUE, color.blue); XMLWriter.end(writer, level, tag_name); writer.println(); } /** * Load RGB color from XML document * * @param node Parent node of the color * @param color_tag Name of tag that contains the color * @return RGB or <code>null</code> if no color found */ static RGB loadColorFromDocument(final Element node, final String color_tag) { if (node == null) return new RGB(0, 0, 0); final Element color = DOMHelper.findFirstElementNode(node.getFirstChild(), color_tag); if (color == null) return null; final int red = DOMHelper.getSubelementInt(color, Model.TAG_RED, 0); final int green = DOMHelper.getSubelementInt(color, Model.TAG_GREEN, 0); final int blue = DOMHelper.getSubelementInt(color, Model.TAG_BLUE, 0); return new RGB(red, green, blue); } /** * Load RGB color from XML document * * @param node Parent node of the color * @return RGB or <code>null</code> if no color found */ static RGB loadColorFromDocument(final Element node) { return loadColorFromDocument(node, Model.TAG_COLOR); } /** * Write XML formatted Model content. * * @param out OutputStream, will be closed when done. */ public void write(final OutputStream out) { final PrintWriter writer = new PrintWriter(out); XMLWriter.header(writer); XMLWriter.start(writer, 0, TAG_DATABROWSER); writer.println(); // L.PHILIPPE // Save config graph settings XYGraphSettingsXMLUtil XYGraphMemXML = new XYGraphSettingsXMLUtil(graphSettings); XYGraphMemXML.write(writer); // Time axis XMLWriter.XML(writer, 1, TAG_SCROLL, isScrollEnabled()); XMLWriter.XML(writer, 1, TAG_UPDATE_PERIOD, getUpdatePeriod()); if (isScrollEnabled()) { XMLWriter.XML(writer, 1, TAG_START, new RelativeTime(-getTimespan())); XMLWriter.XML(writer, 1, TAG_END, RelativeTime.NOW); } else { XMLWriter.XML(writer, 1, TAG_START, getStartTime()); XMLWriter.XML(writer, 1, TAG_END, getEndTime()); } // Time axis config if (timeAxis != null) { XMLWriter.start(writer, 1, TAG_TIME_AXIS); writer.println(); timeAxis.write(writer); XMLWriter.end(writer, 1, TAG_TIME_AXIS); writer.println(); } // Misc. writeColor(writer, 1, TAG_BACKGROUND, background); XMLWriter.XML(writer, 1, TAG_ARCHIVE_RESCALE, archive_rescale.name()); // Value axes XMLWriter.start(writer, 1, TAG_AXES); writer.println(); for (AxisConfig axis : axes) axis.write(writer); XMLWriter.end(writer, 1, TAG_AXES); writer.println(); // Annotations XMLWriter.start(writer, 1, TAG_ANNOTATIONS); writer.println(); for (AnnotationInfo annotation : annotations) annotation.write(writer); XMLWriter.end(writer, 1, TAG_ANNOTATIONS); writer.println(); // PVs (Formulas) XMLWriter.start(writer, 1, TAG_PVLIST); writer.println(); for (ModelItem item : items) item.write(writer); XMLWriter.end(writer, 1, TAG_PVLIST); writer.println(); XMLWriter.end(writer, 0, TAG_DATABROWSER); writer.close(); } public void setTimeAxis(AxisConfig timeAxis) { this.timeAxis = timeAxis; } /** * Read XML formatted Model content. * * @param stream InputStream, will be closed when done. * @throws Exception on error * @throws RuntimeException if model was already in use */ public void read(final InputStream stream) throws Exception { final DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); final Document doc = docBuilder.parse(stream); loadFromDocument(doc); } /** * Load model * * @param doc DOM document * @throws Exception on error * @throws RuntimeException if model was already in use */ private void loadFromDocument(final Document doc) throws Exception { if (is_running || items.size() > 0) throw new RuntimeException("Model was already in use"); // Check if it's a <databrowser/>. doc.getDocumentElement().normalize(); final Element root_node = doc.getDocumentElement(); if (!root_node.getNodeName().equals(TAG_DATABROWSER)) throw new Exception("Wrong document type"); synchronized (this) { scroll_enabled = DOMHelper.getSubelementBoolean(root_node, TAG_SCROLL, scroll_enabled); } update_period = DOMHelper.getSubelementDouble(root_node, TAG_UPDATE_PERIOD, update_period); final String start = DOMHelper.getSubelementString(root_node, TAG_START); final String end = DOMHelper.getSubelementString(root_node, TAG_END); if (start.length() > 0 && end.length() > 0) { final StartEndTimeParser times = new StartEndTimeParser(start, end); setTimerange( TimestampHelper.fromCalendar(times.getStart()), TimestampHelper.fromCalendar(times.getEnd())); } RGB color = loadColorFromDocument(root_node, TAG_BACKGROUND); if (color != null) background = color; try { archive_rescale = ArchiveRescale.valueOf(DOMHelper.getSubelementString(root_node, TAG_ARCHIVE_RESCALE)); } catch (Throwable ex) { archive_rescale = ArchiveRescale.STAGGER; } // Load Time Axis final Element timeAxisNode = DOMHelper.findFirstElementNode(root_node.getFirstChild(), TAG_TIME_AXIS); if (timeAxisNode != null) { // Load PV items Element axisNode = DOMHelper.findFirstElementNode(timeAxisNode.getFirstChild(), TAG_AXIS); timeAxis = AxisConfig.fromDocument(axisNode); } // Load value Axes Element list = DOMHelper.findFirstElementNode(root_node.getFirstChild(), TAG_AXES); if (list != null) { // Load PV items Element item = DOMHelper.findFirstElementNode(list.getFirstChild(), TAG_AXIS); while (item != null) { addAxis(AxisConfig.fromDocument(item)); item = DOMHelper.findNextElementNode(item, TAG_AXIS); } } // Load Annotations list = DOMHelper.findFirstElementNode(root_node.getFirstChild(), TAG_ANNOTATIONS); if (list != null) { // Load PV items Element item = DOMHelper.findFirstElementNode(list.getFirstChild(), TAG_ANNOTATION); final List<AnnotationInfo> infos = new ArrayList<AnnotationInfo>(); try { while (item != null) { final AnnotationInfo annotation = AnnotationInfo.fromDocument(item); infos.add(annotation); item = DOMHelper.findNextElementNode(item, TAG_ANNOTATION); } } catch (Throwable ex) { Activator.getLogger().log(Level.INFO, "XML error in Annotation", ex); } // Add to document annotations = infos.toArray(new AnnotationInfo[infos.size()]); } // ADD by Laurent PHILIPPE // Load Title and graph settings try { graphSettings = XYGraphSettingsXMLUtil.fromDocument(root_node.getFirstChild()); } catch (Throwable ex) { Activator.getLogger().log(Level.INFO, "XML error in Title or graph settings", ex); } // Backwards compatibility with previous data browser which // used global buffer size for all PVs final int buffer_size = DOMHelper.getSubelementInt(root_node, Model.TAG_LIVE_SAMPLE_BUFFER_SIZE, -1); // Load PVs/Formulas list = DOMHelper.findFirstElementNode(root_node.getFirstChild(), TAG_PVLIST); if (list != null) { // Load PV items Element item = DOMHelper.findFirstElementNode(list.getFirstChild(), TAG_PV); while (item != null) { final PVItem model_item = PVItem.fromDocument(this, item); if (buffer_size > 0) model_item.setLiveCapacity(buffer_size); // Adding item creates the axis for it if not already there addItem(model_item); // Backwards compatibility with previous data browser which // stored axis configuration with each item: Update axis from that. final AxisConfig axis = model_item.getAxis(); String s = DOMHelper.getSubelementString(item, TAG_AUTO_SCALE); if (s.equalsIgnoreCase("true")) axis.setAutoScale(true); s = DOMHelper.getSubelementString(item, TAG_LOG_SCALE); if (s.equalsIgnoreCase("true")) axis.setLogScale(true); final double min = DOMHelper.getSubelementDouble(item, Model.TAG_MIN, axis.getMin()); final double max = DOMHelper.getSubelementDouble(item, Model.TAG_MAX, axis.getMax()); axis.setRange(min, max); item = DOMHelper.findNextElementNode(item, TAG_PV); } // Load Formulas item = DOMHelper.findFirstElementNode(list.getFirstChild(), TAG_FORMULA); while (item != null) { addItem(FormulaItem.fromDocument(this, item)); item = DOMHelper.findNextElementNode(item, TAG_FORMULA); } } } }