/// /// This file is part of ILNumerics Community Edition. /// /// ILNumerics Community Edition - high performance computing for applications. /// Copyright (C) 2006 - 2012 Haymo Kutschbach, http://ilnumerics.net /// /// ILNumerics Community Edition is free software: you can redistribute it and/or modify /// it under the terms of the GNU General Public License version 3 as published by /// the Free Software Foundation. /// /// ILNumerics Community Edition is distributed in the hope that it will be useful, /// but WITHOUT ANY WARRANTY; without even the implied warranty of /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the /// GNU General Public License for more details. /// /// You should have received a copy of the GNU General Public License /// along with ILNumerics Community Edition. See the file License.txt in the root /// of your distribution package. If not, see . /// /// In addition this software uses the following components and/or licenses: /// /// ================================================================================= /// The Open Toolkit Library License /// /// Copyright (c) 2006 - 2009 the Open Toolkit library. /// /// Permission is hereby granted, free of charge, to any person obtaining a copy /// of this software and associated documentation files (the "Software"), to deal /// in the Software without restriction, including without limitation the rights to /// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of /// the Software, and to permit persons to whom the Software is furnished to do /// so, subject to the following conditions: /// /// The above copyright notice and this permission notice shall be included in all /// copies or substantial portions of the Software. /// /// ================================================================================= /// #pragma warning disable 1591 using System; using System.Collections.Generic; using System.Text; using ILNumerics.Exceptions; using ILNumerics.Drawing.Misc; using ILNumerics.Drawing.Interfaces; using ILNumerics.Drawing.Graphs; using ILNumerics.Drawing.Plots; namespace ILNumerics.Drawing.Collections { /// /// Collection of graph objects - all graphs currently contained in a subfigure /// public sealed class ILGraphCollection : List, IDisposable { public event ILGraphCollectionChangedEvent CollectionChanged; public event ILGraphChangedEvent GraphChanged; /// /// triggers the ILGraphCollectionChanged event /// /// /// void OnChange (ILGraph graph, GraphCollectionChangeReason reason, IILPanelConfigurator configurator) { if (CollectionChanged != null) CollectionChanged(this, new ILGraphCollectionChangedEventArgs((ILGraph)graph, reason, configurator)); } /// /// triggers GraphChanged event, bubbles from single graph /// /// event arguments from graph void OnGraphChanged(ILGraph sender, ILGraphChangedEventArgs graphArgs) { if (GraphChanged != null) { GraphChanged(sender,graphArgs); } } #region members / properties private IILCreationFactory m_graphFact; private ILClippingData m_clippingData; private List m_unsortedCache; private ILGraphComparer m_graphComparer; /// /// Clippping volume for data in all graphs of the collection /// /// This gives back the real ILClippingData object (no copy) public ILClippingData Limits { get { return m_clippingData; } } #endregion #region constructor / disposal /// /// Create new ILGraphCollection /// /// Output panel /// internal ILGraphCollection(IILCreationFactory vPainterFact) : base() { m_graphFact = vPainterFact; m_clippingData = new ILClippingData(); m_unsortedCache = new List(); m_graphComparer = new ILGraphComparer(); } /// /// dispose all graphs contained in this collection /// public void Dispose() { foreach (ILGraph graph in this) { if (graph != null) graph.Dispose(); } } #endregion #region event handling ///// ///// signaled when one/some of the ILGraphs have changed ///// ///// ///// //void Graph_Changed(object sender, ILGraphChangedEventArgs e) { // OnGraphChanged(sender as ILGraph,e); //} /// /// called once the limits of a graph have changed /// /// graph /// event args, holding a reference to the clipping data void Limits_Changed(object sender, ClippingChangedEventArgs e) { ILPoint3Df max = ILPoint3Df.MinValue; ILPoint3Df min = ILPoint3Df.MaxValue; foreach (ILGraph graph in this) { max = ILPoint3Df.Max(max,graph.Limits.Max); min = ILPoint3Df.Min(min,graph.Limits.Min); } m_clippingData.Set(min,max); } #endregion #region Collection manager /// /// clear all graphs from the collection /// public new void Clear() { lock (this) { foreach (ILGraph g in this) { g.Dispose(); } base.Clear(); } m_clippingData.Reset(); Invalidate(); OnChange(null,GraphCollectionChangeReason.Deleted,null); } /// /// Add a new surface graph, provide all coordinate data /// /// matrix holding Z coordinates (heights) /// reference to newly created surface graph public ILSurfaceGraph AddSurfGraph(ILInArray X, ILInArray Y, ILInArray Z, ILInArray C) { using (ILScope.Enter(X, Y, Z, C)) { ILSurfaceGraph ret; ret = (ILSurfaceGraph)m_graphFact.CreateGraph(Z, GraphType.Surf, X, Y, C); m_clippingData.Update(ret.Limits); Add(ret); ret.Changed += new ILGraphChangedEvent(GraphChanged); // trigger change event OnChange(ret, GraphCollectionChangeReason.Added, null); return ret; } } /// /// Add a new surface graph to collection /// /// matrix holding data to be plotted /// reference to newly created surface graph public ILSurfaceGraph AddSurfGraph(ILInArray data) { using (ILScope.Enter(data)) { return (ILSurfaceGraph)Add(data, GraphType.Surf)[0]; } } /// /// add a plot to a new scene graph /// /// plot to be added to the panel /// newly created scene graph public ILSceneGraph AddPlot(ILPlot plot) { ILSceneGraph scene = AddSceneGraph(); scene.AddNode(plot); return scene; } /// /// Add a new scene graph to collection /// /// reference to newly created surface graph public ILSceneGraph AddSceneGraph() { ILSceneGraph newGraph = m_graphFact.CreateSceneGraph(); Add(newGraph); newGraph.Changed += new ILGraphChangedEvent(GraphChanged); newGraph.Limits.Changed += new ILClippingDataChangedEvent(Limits_Changed); newGraph.NodeAdded += new SceneGraphNodeHandler(sceneGraph_NodeAdded); OnChange(newGraph, GraphCollectionChangeReason.Added, null); return newGraph; } void sceneGraph_NodeAdded(object sender, ILSceneGraphNodeEventArgs args) { if (args.Node != null && args.Node is IILPanelConfigurator) { OnChange(sender as ILGraph, GraphCollectionChangeReason.Added, args.Node as IILPanelConfigurator); } } /// /// add an newly created scene graph instance to the collection of graphs /// /// exisisting instance of scene graph /// This overload may be used to add user defined graphs to /// the collection of graphs in order to use them in ILPanels. The user /// defined graph needs to derive from scene graph. It may uses all shapes /// compatible with ILSceneGraph. (ILLine,ILPolygon,ILLitBox,ILQuads,ILTriangles, etc...) /// public void AddSceneGraph(ILSceneGraph sceneGraph) { if (sceneGraph == null) throw new ILArgumentException("scene graph must not be null!"); Add(sceneGraph); sceneGraph.Changed += new ILGraphChangedEvent(GraphChanged); sceneGraph.Limits.Changed += new ILClippingDataChangedEvent(Limits_Changed); OnChange(sceneGraph, GraphCollectionChangeReason.Added, null); } /// /// Add a new imagesc graph to collection /// /// matrix holding data to be drawn /// newly created graph public ILImageSCGraph AddImageSCGraph(ILInArray data) { using (ILScope.Enter(data)) return (ILImageSCGraph)Add(data,GraphType.Imagesc)[0]; } /// /// Add line graph(s) /// /// vector or matrix with date to be plotted. For matrices, the columns will be used to create new graphs. /// Array of newly created graph(s) public ILPlot2DGraph[] AddLineGraph(ILInArray A) { using (ILScope.Enter(A)) { return AddLineGraph(ILMath.tosingle(A)); } } /// /// Add line graph(s) /// /// vector or matrix with date to be plotted. For matrices, the columns will be used to create new graphs. /// Array of newly created graph(s) public ILPlot2DGraph[] AddLineGraph(ILInArray A) { using (ILScope.Enter(A)) { List graphs = Add(A, GraphType.Plot2D); List ret = new List(); ILColorEnumerator cenumerator = new ILColorEnumerator(Colormaps.Lines); foreach (ILGraph g in graphs) { ILPlot2DGraph plot2DGraph = (ILPlot2DGraph)g; ret.Add(plot2DGraph); plot2DGraph.Line.Color = cenumerator.NextColor(); } return ret.ToArray(); } } /// /// Add X/Y line graph(s) /// /// X coordinates. If this is a matrix, every column provides data for an individual graph. /// Y coordinates. Same size as 'X'. /// newly created graph(s) public ILPlot2DGraph[] AddLineGraph(ILInArray XData, ILInArray YData) { using (ILScope.Enter(XData,YData)) { return AddLineGraph(ILMath.tosingle(XData), ILMath.tosingle(YData)); } } /// /// Add X/Y line graph(s) /// /// X coordinates. If this is a matrix, every column provides data for an individual graph. /// Y coordinates. Same size as 'X'. /// newly created graph(s) public ILPlot2DGraph[] AddLineGraph(ILInArray XData, ILInArray YData) { using (ILScope.Enter(XData, YData)) { List graphs = Add(XData, YData, GraphType.Plot2D); List ret = new List(); ILColorEnumerator cenumerator = new ILColorEnumerator(Colormaps.Lines); foreach (ILGraph g in graphs) { ILPlot2DGraph plot2DGraph = (ILPlot2DGraph)g; ret.Add(plot2DGraph); plot2DGraph.Line.Color = cenumerator.NextColor(); } return ret.ToArray(); } } /// /// [Depricated] Add new line graph(s) (2D) to collection /// /// vector/array with date to be plotted. For arrays, the columns will be used. /// Array of newly created graph(s) public ILPlot2DGraph[] AddPlot2DGraph(ILInArray data) { return AddLineGraph(data); } /// /// [Depricated] Add new X/Y 2D line graph(s), provide X and Y coordinates /// /// X coordinates. If this is a matrix, every column will produce an individual graph. /// Y coordinates. Same size than 'X'. /// Array of newly created graph(s) public ILPlot2DGraph[] AddPlot2DGraph(ILInArray XData, ILInArray YData) { return AddLineGraph(XData, YData); } /// /// Add new numeric graph(s) of arbitrary (math) type /// /// data to be plotted /// determine GraphType /// List with newly created graph(s) public List Add(ILInArray data, GraphType graphType) { using (ILScope.Enter(data)) { if (!data.IsNumeric) throw new ILArgumentException("Add graph: data array must be numeric!"); List ret = new List(); ILGraph newGraph; ILArray tmpData; lock (this) { #region add each graph type seperately switch (graphType) { case GraphType.Plot2D: if (data.IsVector || data.IsScalar) { newGraph = m_graphFact.CreateGraph(data, graphType); Add(newGraph); newGraph.Changed += new ILGraphChangedEvent(GraphChanged); m_clippingData.Update(newGraph.Limits); newGraph.Limits.Changed += new ILClippingDataChangedEvent(Limits_Changed); ret.Add(newGraph); } else if (data.IsMatrix) { // plot columns m_clippingData.EventingSuspend(); tmpData = ILMath.tosingle(data); for (int c = 0; c < tmpData.Size[1]; c++) { newGraph = m_graphFact.CreateGraph(tmpData[":", c], graphType); Add(newGraph); newGraph.Changed += new ILGraphChangedEvent(GraphChanged); ret.Add(newGraph); m_clippingData.Update(newGraph.Limits); newGraph.Limits.Changed += new ILClippingDataChangedEvent(Limits_Changed); } m_clippingData.EventingResume(); } // trigger change event OnChange(ret[0], GraphCollectionChangeReason.Added, ret[0] as IILPanelConfigurator); break; case GraphType.Surf: if (!data.IsMatrix) throw new ILArgumentException("Surf: source data must be a matrix!"); tmpData = ILMath.tosingle(data); m_clippingData.EventingSuspend(); // margin for Z-Direction float min, max; tmpData.GetLimits(out min, out max); float zmarg = (max - min > 0) ? (float)(Math.Abs(max - min) / 10.0) : 0.0f; m_clippingData.Update(new ILPoint3Df(0, 0, min - zmarg), new ILPoint3Df(data.Size[1] - 1, data.Size[0] - 1, max + zmarg)); m_clippingData.EventingResume(); newGraph = m_graphFact.CreateGraph(tmpData, graphType); Add(newGraph); newGraph.Changed += new ILGraphChangedEvent(GraphChanged); // trigger change event ret.Add(newGraph); OnChange(newGraph, GraphCollectionChangeReason.Added, newGraph as IILPanelConfigurator); break; case GraphType.Imagesc: if (!data.IsMatrix) throw new ILArgumentException("Surf: source data must be a matrix!"); tmpData = ILMath.tosingle(data); // note, ImageSC does not contribute to global clipping, except that the clipping // will be updated to include z = 0! m_clippingData.EventingSuspend(); m_clippingData.Update(new ILPoint3Df(-0.5f, -.5f, 0), new ILPoint3Df(data.Size[1] - 0.5f, data.Size[0] - 0.5f, 0)); m_clippingData.EventingResume(); newGraph = m_graphFact.CreateGraph(tmpData, graphType); Add(newGraph); newGraph.Changed += new ILGraphChangedEvent(GraphChanged); // trigger change event OnChange(newGraph, GraphCollectionChangeReason.Added, newGraph as IILPanelConfigurator); ret.Add(newGraph); break; default: throw new ILDrawingException("the type of drawing is not supported!"); } #endregion } return ret; } } /// /// Add new graph(s) of arbitrary type, provide both axis data /// /// x coordinates /// type of graph to be added /// y coordinates /// List with newly created graph(s) /// The return value will be a list of all graphs created (and added), /// since more than one graph may have been specified. This depends on the /// shape of the data provided. /// Currently only Plot2D graphs are supported as GraphType! /// if the data provided are nor /// numeric or the size for one of the arrays or /// do not match. public List Add(ILInArray xData, ILInArray yData, GraphType graphType) { using (ILScope.Enter(xData, yData)) { if (!yData.IsNumeric || !xData.IsNumeric) throw new ILArgumentException("Add graph: data arrays must be numeric!"); List ret = new List(); ILGraph newGraph; ILArray tmpDataY, tmpDataX; lock (this) { #region add each graph type seperately switch (graphType) { case GraphType.Plot2D: if (!yData.Size.IsSameSize(xData.Size)) throw new ILArgumentException("Add graph: for X/Y plots, the size of X and Y must be equal!"); if (yData.IsVector || yData.IsScalar) { newGraph = m_graphFact.CreateGraph(yData, graphType, xData); Add(newGraph); newGraph.Changed += new ILGraphChangedEvent(GraphChanged); m_clippingData.Update(newGraph.Limits); ret.Add(newGraph); } else if (yData.IsMatrix) { // plot columns m_clippingData.EventingSuspend(); tmpDataY = ILMath.tosingle(yData); tmpDataX = ILMath.tosingle(xData); for (int c = 0; c < tmpDataY.Size[1]; c++) { newGraph = m_graphFact.CreateGraph(tmpDataY[":", c], graphType, tmpDataX[":", c]); Add(newGraph); newGraph.Changed += new ILGraphChangedEvent(GraphChanged); ret.Add(newGraph); m_clippingData.Update(newGraph.Limits); } m_clippingData.EventingResume(); } // trigger change event OnChange(ret[0], GraphCollectionChangeReason.Added, ret[0] as IILPanelConfigurator); break; default: throw new ILDrawingException("graph type is not supported in that mode yet!"); } #endregion } return ret; } } /// /// Remove a graph from the collection and rescale data limits /// /// key of graph to be removed /// the ILGraph removed or null, if the key does not exist /// If the graph has been removed, the clipping data will be updated and a change event will be fired. public new bool Remove(ILGraph graph) { lock (this) { if (!Contains(graph)) return false; base.Remove(graph); // recreate clipping regions m_clippingData.EventingSuspend(); m_clippingData.Reset(); foreach (ILGraph gr in this) { m_clippingData.Update (gr.Limits); } m_clippingData.EventingResume(); OnChange(graph,GraphCollectionChangeReason.Deleted,null); Invalidate(); return true; } } #endregion #region public interface public void Invalidate() { foreach (ILGraph g in this) { g.Invalidate(); } } internal void GetSortedList4Render(ILCamera camera, List sortList) { m_unsortedCache.Clear(); foreach (ILGraph g in this) { if (g.Is3DGraph()) { sortList.Add(g); } else { m_unsortedCache.Add(g); } } m_graphComparer.Camera = camera; sortList.Sort(m_graphComparer); sortList.AddRange(m_unsortedCache); } #endregion #region private helper #endregion } }