StaticPlot.py revision b42593d6a0cabfc64d415820c682f652b70acb64
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, **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        permute = self._attr["permute"] and not self._attr["concat"]
172        if self._attr["style"]:
173            with plt.rc_context(AttrConf.MPL_STYLE):
174                self._resolve(permute, self._attr["concat"])
175        else:
176            self._resolve(permute, self._attr["concat"])
177
178    def make_title(self, constraint, pivot, permute, concat):
179        """Generates a title string for an axis"""
180        if concat:
181            return str(constraint)
182        title = ""
183        if permute:
184            title += constraint.get_data_name() + ":"
185
186        if pivot == AttrConf.PIVOT_VAL:
187            if isinstance(self._attr["column"], list):
188                title += ", ".join(self._attr["column"])
189            else:
190                title += self._attr["column"]
191        else:
192            title += "{0}: {1}".format(self._attr["pivot"],
193                                       self._attr["map_label"].get(pivot, pivot))
194        return title
195
196    def add_to_legend(self, series_index, handle, constraint, pivot, concat):
197        """
198        Add series handles and names to the legend
199        A handle is returned from a plot on an axis
200        e.g. Line2D from axis.plot()
201        """
202        self._attr["_legend_handles"][series_index] = handle
203        legend_labels = self._attr["_legend_labels"]
204
205        if concat and pivot == AttrConf.PIVOT_VAL:
206            legend_labels[series_index] = self._attr["column"]
207        elif concat:
208            legend_labels[series_index] = "{0}: {1}".format(
209                self._attr["pivot"],
210                self._attr["map_label"].get(pivot, pivot)
211            )
212        else:
213            legend_labels[series_index] = str(constraint)
214            # Remove trace name if there is only one trace to plot
215            if not isinstance(self.traces, list):
216                legend_labels[series_index] = legend_labels[series_index].replace(
217                                                constraint.get_data_name()+":", ""
218                                              )
219
220    def _resolve(self, permute, concat):
221        """Determine what data to plot on which axis"""
222        pivot_vals, len_pivots = self.c_mgr.generate_pivots(permute)
223        pivot_vals = list(pivot_vals)
224
225        num_of_axes = len(self.c_mgr) if concat else len_pivots
226
227        # Create a 2D Layout
228        self._layout = PlotLayout(
229            self._attr["per_line"],
230            num_of_axes,
231            width=self._attr["width"],
232            length=self._attr["length"],
233            title=self._attr['title'])
234
235        self._fig = self._layout.get_fig()
236
237        #Determine what constraint to plot and the corresponding pivot value
238        if permute:
239            legend_len = self.c_mgr._max_len
240            pivots = [y for _, y in pivot_vals]
241            cp_pairs = [(c, p) for c in self.c_mgr for p in sorted(set(pivots))]
242        else:
243            legend_len = len_pivots if concat else len(self.c_mgr)
244            pivots = pivot_vals
245            cp_pairs = [(c, p) for c in self.c_mgr for p in pivots if p in c.result]
246
247        #Initialise legend data and colormap
248        self._attr["_legend_handles"] = [None] * legend_len
249        self._attr["_legend_labels"] = [None] * legend_len
250        self._cmap = ColorMap(legend_len)
251
252        #Group constraints/series with the axis they are to be plotted on
253        figure_data = ddict(list)
254        for i, (constraint, pivot) in enumerate(cp_pairs):
255            axis = self._layout.get_axis(constraint.trace_index if concat else i)
256            figure_data[axis].append((constraint, pivot))
257
258        #Plot each axis
259        for axis, series_list in figure_data.iteritems():
260            self.plot_axis(
261                axis,
262                series_list,
263                permute,
264                self._attr["concat"],
265                self._attr["args_to_forward"]
266            )
267
268        #Add the legend to the figure if more than one signal is plotted
269        if legend_len > 1:
270            self._fig.legend(self._attr["_legend_handles"],
271                             self._attr["_legend_labels"])
272
273        self._layout.finish(num_of_axes)
274
275    def plot_axis(self, axis, series_list, permute, concat, args_to_forward):
276        """Internal Method called to plot data (series_list) on a given axis"""
277        raise NotImplementedError("Method Not Implemented")
278