StaticPlot.py revision 5d35796164c77d1350b7dc26329fb0d0abc549b5
1#    Copyright 2016-2016 ARM Limited
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14#
15"""Base matplotlib plotter module"""
16from abc import abstractmethod, ABCMeta
17from collections import defaultdict as ddict
18import matplotlib.pyplot as plt
19from trappy.plotter import AttrConf
20from trappy.plotter.Constraint import ConstraintManager
21from trappy.plotter.PlotLayout import PlotLayout
22from trappy.plotter.AbstractDataPlotter import AbstractDataPlotter
23from trappy.plotter.ColorMap import ColorMap
24
25
26
27class StaticPlot(AbstractDataPlotter):
28    """
29    This class uses :mod:`trappy.plotter.Constraint.Constraint` to
30    represent different permutations of input parameters. These
31    constraints are generated by creating an instance of
32    :mod:`trappy.plotter.Constraint.ConstraintManager`.
33
34    :param trace: The input data
35    :type trace: :mod:`trappy.trace.FTrace` or :mod:`pandas.DataFrame`, list or single
36
37    :param column: specifies the name of the column to
38           be plotted.
39    :type column: (str, list(str))
40
41    :param templates: TRAPpy events
42
43        .. note::
44
45                This is not required if a :mod:`pandas.DataFrame` is
46                used
47
48    :type templates: :mod:`trappy.base.Base`
49
50    :param filters: Filter the column to be plotted as per the
51        specified criteria. For Example:
52        ::
53
54            filters =
55                    {
56                        "pid": [ 3338 ],
57                        "cpu": [0, 2, 4],
58                    }
59    :type filters: dict
60
61    :param per_line: Used to control the number of graphs
62        in each graph subplot row
63    :type per_line: int
64
65    :param concat: Draw all the pivots on a single graph
66    :type concat: bool
67
68    :param permute: Draw one plot for each of the traces specified
69    :type permute: bool
70
71    :param drawstyle: This argument is forwarded to the matplotlib
72        corresponding :func:`matplotlib.pyplot.plot` call
73
74        drawing style.
75
76        .. note::
77
78            step plots are not currently supported for filled
79            graphs
80
81    :param xlim: A tuple representing the upper and lower xlimits
82    :type xlim: tuple
83
84    :param ylim: A tuple representing the upper and lower ylimits
85    :type ylim: tuple
86
87    :param title: A title describing all the generated plots
88    :type title: str
89
90    :param style: Created pre-styled graphs loaded from
91        :mod:`trappy.plotter.AttrConf.MPL_STYLE`
92    :type style: bool
93
94    :param signals: A string of the type event_name:column
95        to indicate the value that needs to be plotted
96
97        .. note::
98
99            - Only one of `signals` or both `templates` and
100              `columns` should be specified
101            - Signals format won't work for :mod:`pandas.DataFrame`
102              input
103
104    :type signals: str
105
106    """
107    __metaclass__ = ABCMeta
108
109    def __init__(self, traces, templates=None, **kwargs):
110        self._fig = None
111        self._layout = None
112        super(StaticPlot, self).__init__(traces=traces,
113                                         templates=templates)
114
115        self.set_defaults()
116
117        for key in kwargs:
118            if key in AttrConf.ARGS_TO_FORWARD:
119                self._attr["args_to_forward"][key] = kwargs[key]
120            else:
121                self._attr[key] = kwargs[key]
122
123        if "signals" in self._attr:
124            self._describe_signals()
125
126        self._check_data()
127
128        if "column" not in self._attr:
129            raise RuntimeError("Value Column not specified")
130
131        zip_constraints = not self._attr["permute"]
132        self.c_mgr = ConstraintManager(traces, self._attr["column"],
133                                       self.templates, self._attr["pivot"],
134                                       self._attr["filters"], zip_constraints)
135
136    def savefig(self, *args, **kwargs):
137        """Save the plot as a PNG fill. This calls into
138        :mod:`matplotlib.figure.savefig`
139        """
140
141        if self._fig is None:
142            self.view()
143        self._fig.savefig(*args, **kwargs)
144
145    @abstractmethod
146    def set_defaults(self):
147        """Sets the default attrs"""
148        self._attr["width"] = AttrConf.WIDTH
149        self._attr["length"] = AttrConf.LENGTH
150        self._attr["per_line"] = AttrConf.PER_LINE
151        self._attr["concat"] = AttrConf.CONCAT
152        self._attr["filters"] = {}
153        self._attr["style"] = True
154        self._attr["permute"] = False
155        self._attr["pivot"] = AttrConf.PIVOT
156        self._attr["xlim"] = AttrConf.XLIM
157        self._attr["ylim"] = AttrConf.XLIM
158        self._attr["title"] = AttrConf.TITLE
159        self._attr["args_to_forward"] = {}
160        self._attr["map_label"] = {}
161        self._attr["_legend_handles"] = []
162        self._attr["_legend_labels"] = []
163
164    def view(self, test=False):
165        """Displays the graph"""
166
167        if test:
168            self._attr["style"] = True
169            AttrConf.MPL_STYLE["interactive"] = False
170
171        if self._attr["concat"]:
172            if self._attr["style"]:
173                with plt.rc_context(AttrConf.MPL_STYLE):
174                    self._resolve_concat()
175            else:
176                self._resolve_concat()
177        else:
178            if self._attr["style"]:
179                with plt.rc_context(AttrConf.MPL_STYLE):
180                    self._resolve(self._attr["permute"])
181            else:
182                self._resolve(self._attr["permute"])
183
184    def make_title(self, constraint, pivot, permute, concat):
185        """Generates a title string for an axis"""
186        if concat:
187            return str(constraint)
188        title = ""
189        if permute:
190            title += constraint.get_data_name() + ":"
191
192        if pivot == AttrConf.PIVOT_VAL:
193            if isinstance(self._attr["column"], list):
194                title += ", ".join(self._attr["column"])
195            else:
196                title += self._attr["column"]
197        else:
198            title += "{0}: {1}".format(self._attr["pivot"],
199                                       self._attr["map_label"].get(pivot, pivot))
200        return title
201
202    def add_to_legend(self, series_index, handle, constraint, pivot, concat):
203        """
204        Add series handles and names to the legend
205        A handle is returned from a plot on an axis
206        e.g. Line2D from axis.plot()
207        """
208        self._attr["_legend_handles"][series_index] = handle
209        legend_labels = self._attr["_legend_labels"]
210
211        if concat and pivot == AttrConf.PIVOT_VAL:
212            legend_labels[series_index] = self._attr["column"]
213        elif concat:
214            legend_labels[series_index] = "{0}: {1}".format(
215                self._attr["pivot"],
216                self._attr["map_label"].get(pivot, pivot)
217            )
218        else:
219            legend_labels[series_index] = str(constraint)
220
221    def _resolve(self, permute=False):
222        """Determine what data to plot"""
223        pivot_vals, len_pivots = self.c_mgr.generate_pivots(permute)
224        pivot_vals = list(pivot_vals)
225
226        # Create a 2D Layout
227        self._layout = PlotLayout(
228            self._attr["per_line"],
229            len_pivots,
230            width=self._attr["width"],
231            length=self._attr["length"],
232            title=self._attr['title'])
233
234        self._fig = self._layout.get_fig()
235
236        #Determine what constraint to plot and the corresponding pivot value
237        if permute:
238            legend_len = self.c_mgr._max_len
239            pivots = [y for _, y in pivot_vals]
240            cp_pairs = [(c, p) for c in self.c_mgr for p in sorted(set(pivots))]
241        else:
242            legend_len = len(self.c_mgr)
243            pivots = pivot_vals
244            cp_pairs = [(c, p) for c in self.c_mgr for p in pivots if p in c.result]
245
246        #Initialise legend data and colormap
247        self._attr["_legend_handles"] = [None] * legend_len
248        self._attr["_legend_labels"] = [None] * legend_len
249        self._cmap = ColorMap(legend_len)
250
251        #Group constraints/series with the axis they are to be plotted on
252        figure_data = ddict(list)
253        for i, (constraint, pivot) in enumerate(cp_pairs):
254            axis = self._layout.get_axis(i)
255            figure_data[axis].append((constraint, pivot))
256
257        #Plot each axis
258        for axis, series_list in figure_data.iteritems():
259            self.plot_axis(
260                axis,
261                series_list,
262                permute,
263                self._attr["concat"],
264                args_to_forward=self._attr["args_to_forward"]
265            )
266
267        #Add the legend to the figure
268        self._fig.legend(self._attr["_legend_handles"],
269                         self._attr["_legend_labels"])
270        self._layout.finish(len_pivots)
271
272    def _resolve_concat(self):
273        """Plot all lines on a single figure"""
274        pivot_vals, len_pivots = self.c_mgr.generate_pivots(False)
275        pivot_vals = list(pivot_vals)
276
277        # Create a 2D Layout
278        self._layout = PlotLayout(
279            self._attr["per_line"],
280            len(self.c_mgr),
281            width=self._attr["width"],
282            length=self._attr["length"],
283            title=self._attr['title'])
284
285        self._fig = self._layout.get_fig()
286
287        #Determine what constraint to plot and the corresponding pivot value
288        legend_len = len_pivots
289        pivots = pivot_vals
290        cp_pairs = [(c, p) for c in self.c_mgr for p in pivots if p in c.result]
291
292        #Initialise legend data and colormap
293        self._attr["_legend_handles"] = [None] * legend_len
294        self._attr["_legend_labels"] = [None] * legend_len
295        self._cmap = ColorMap(legend_len)
296
297        #Group constraints/series with the axis they are to be plotted on
298        figure_data = ddict(list)
299        for i, (constraint, pivot) in enumerate(cp_pairs):
300            axis = self._layout.get_axis(constraint.trace_index)
301            figure_data[axis].append((constraint, pivot))
302
303        #Plot each axis
304        for axis, series_list in figure_data.iteritems():
305            self.plot_axis(
306                axis,
307                series_list,
308                False,
309                self._attr["concat"],
310                self._attr["args_to_forward"]
311            )
312
313        #Add the legend to the figure
314        self._fig.legend(self._attr["_legend_handles"],
315                         self._attr["_legend_labels"])
316        self._layout.finish(len(self.c_mgr))
317
318    def plot_axis(self, axis, series_list, permute, concat, args_to_forward):
319        """Internal Method called to plot data (series_list) on a given axis"""
320        raise NotImplementedError("Method Not Implemented")
321