1#    Copyright 2015-2017 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
16"""This module contains the class for plotting and customizing
17Line/Linear Plots with :mod:`trappy.trace.BareTrace` or derived
18classes.  This plot only works when run from an IPython notebook
19
20"""
21
22from collections import OrderedDict
23import matplotlib.pyplot as plt
24from trappy.plotter import AttrConf
25from trappy.plotter import Utils
26from trappy.plotter.Constraint import ConstraintManager
27from trappy.plotter.ILinePlotGen import ILinePlotGen
28from trappy.plotter.AbstractDataPlotter import AbstractDataPlotter
29from trappy.plotter.ColorMap import ColorMap
30from trappy.plotter import IPythonConf
31from trappy.utils import handle_duplicate_index
32import pandas as pd
33
34if not IPythonConf.check_ipython():
35    raise ImportError("Ipython Environment not Found")
36
37class ILinePlot(AbstractDataPlotter):
38    """
39    This class uses :mod:`trappy.plotter.Constraint.Constraint` to
40    represent different permutations of input parameters. These
41    constraints are generated by creating an instance of
42    :mod:`trappy.plotter.Constraint.ConstraintManager`.
43
44    :param traces: The input data
45    :type traces: a list of :mod:`trappy.trace.FTrace`,
46        :mod:`trappy.trace.SysTrace`, :mod:`trappy.trace.BareTrace`
47        or :mod:`pandas.DataFrame` or a single instance of them.
48
49    :param column: specifies the name of the column to
50           be plotted.
51    :type column: (str, list(str))
52
53    :param templates: TRAPpy events
54
55        .. note::
56
57                This is not required if a :mod:`pandas.DataFrame` is
58                used
59
60    :type templates: :mod:`trappy.base.Base`
61
62    :param filters: Filter the column to be plotted as per the
63        specified criteria. For Example:
64        ::
65
66            filters =
67                    {
68                        "pid": [ 3338 ],
69                        "cpu": [0, 2, 4],
70                    }
71    :type filters: dict
72
73    :param per_line: Used to control the number of graphs
74        in each graph subplot row
75    :type per_line: int
76
77    :param concat: Draw all the pivots on a single graph
78    :type concat: bool
79
80    :param permute: Draw one plot for each of the traces specified
81    :type permute: bool
82
83    :param fill: Fill the area under the plots
84    :type fill: bool
85
86    :param fill_alpha: Opacity of filled area under the plots.
87        Implies fill=True.
88    :type fill_alpha: float
89
90    :param xlim: A tuple representing the upper and lower xlimits
91    :type xlim: tuple
92
93    :param ylim: A tuple representing the upper and lower ylimits
94    :type ylim: tuple
95
96    :param drawstyle: Set the drawstyle to a matplotlib compatible
97        drawing style.
98
99        .. note::
100
101            Only "steps-post" is supported as a valid value for
102            the drawstyle. This creates a step plot.
103
104    :type drawstyle: str
105
106    :param sync_zoom: Synchronize the zoom of a group of plots.
107        Zooming in one plot of a group (see below) will zoom in every
108        plot of that group.  Defaults to False.
109    :type sync_zoom: boolean
110
111    :param group: Name given to the plots created by this ILinePlot
112        instance.  This name is only used for synchronized zoom.  If
113        you zoom on any plot in a group all plots will zoom at the
114        same time.
115    :type group: string
116
117    :param signals: A string of the type event_name:column to indicate
118        the value that needs to be plotted.  You can add an additional
119        parameter to specify the color of the lin in rgb:
120        "event_name:column:color".  The color is specified as a comma
121        separated list of rgb values, from 0 to 255 or from 0x0 to
122        0xff.  E.g. 0xff,0x0,0x0 is red and 100,40,32 is brown.
123
124        .. note::
125
126            - Only one of `signals` or both `templates` and
127              `columns` should be specified
128            - Signals format won't work for :mod:`pandas.DataFrame`
129              input
130
131    :type signals: str
132    """
133
134    def __init__(self, traces, templates=None, **kwargs):
135        # Default keys, each can be overridden in kwargs
136        self._layout = None
137        super(ILinePlot, self).__init__(traces=traces,
138                                        templates=templates)
139
140        self.set_defaults()
141
142        for key in kwargs:
143            self._attr[key] = kwargs[key]
144
145        if "signals" in self._attr:
146            self._describe_signals()
147
148        self._check_data()
149
150        if "column" not in self._attr:
151            raise RuntimeError("Value Column not specified")
152
153        if self._attr["drawstyle"] and self._attr["drawstyle"].startswith("steps"):
154            self._attr["step_plot"] = True
155
156        zip_constraints = not self._attr["permute"]
157
158        window = self._attr["xlim"] if "xlim" in self._attr else None
159
160        self.c_mgr = ConstraintManager(traces, self._attr["column"], self.templates,
161                                       self._attr["pivot"],
162                                       self._attr["filters"],
163                                       window=window,
164                                       zip_constraints=zip_constraints)
165
166
167    def savefig(self, *args, **kwargs):
168        raise NotImplementedError("Not Available for ILinePlot")
169
170    def view(self, max_datapoints=75000, test=False):
171        """Displays the graph
172
173        :param max_datapoints: Maximum number of datapoints to plot.
174        Dygraph can make the browser unresponsive if it tries to plot
175        too many datapoints.  Chrome 50 chokes at around 75000 on an
176        i7-4770 @ 3.4GHz, Firefox 47 can handle up to 200000 before
177        becoming too slow in the same machine.  You can increase this
178        number if you know what you're doing and are happy to wait for
179        the plot to render.  :type max_datapoints: int
180
181        :param test: For testing purposes.  Only set to true if run
182        from the testsuite.
183        :type test: boolean
184        """
185
186        # Defer installation of IPython components
187        # to the .view call to avoid any errors at
188        # when importing the module. This facilitates
189        # the importing of the module from outside
190        # an IPython notebook
191        if not test:
192            IPythonConf.iplot_install("ILinePlot")
193
194        self._attr["max_datapoints"] = max_datapoints
195
196        if self._attr["concat"]:
197            self._plot_concat()
198        else:
199            self._plot(self._attr["permute"], test)
200
201    def set_defaults(self):
202        """Sets the default attrs"""
203        self._attr["per_line"] = AttrConf.PER_LINE
204        self._attr["concat"] = AttrConf.CONCAT
205        self._attr["filters"] = {}
206        self._attr["pivot"] = AttrConf.PIVOT
207        self._attr["permute"] = False
208        self._attr["drawstyle"] = None
209        self._attr["step_plot"] = False
210        self._attr["fill"] = AttrConf.FILL
211        self._attr["scatter"] = AttrConf.PLOT_SCATTER
212        self._attr["point_size"] = AttrConf.POINT_SIZE
213        self._attr["map_label"] = {}
214        self._attr["title"] = AttrConf.TITLE
215
216    def _plot(self, permute, test):
217        """Internal Method called to draw the plot"""
218        pivot_vals, len_pivots = self.c_mgr.generate_pivots(permute)
219
220        self._layout = ILinePlotGen(len_pivots, **self._attr)
221        plot_index = 0
222        for p_val in pivot_vals:
223            data_dict = OrderedDict()
224            for constraint in self.c_mgr:
225                if permute:
226                    trace_idx, pivot = p_val
227                    if constraint.trace_index != trace_idx:
228                        continue
229                    legend = constraint._template.name + ":" + constraint.column
230                else:
231                    pivot = p_val
232                    legend = str(constraint)
233
234                result = constraint.result
235                if pivot in result:
236                    data_dict[legend] = result[pivot]
237
238            if permute:
239                title = self.traces[plot_index].name
240            elif pivot != AttrConf.PIVOT_VAL:
241                title = "{0}: {1}".format(self._attr["pivot"], self._attr["map_label"].get(pivot, pivot))
242            else:
243                title = ""
244
245            if len(data_dict) > 1:
246                data_frame = self._fix_indexes(data_dict)
247            else:
248                data_frame = pd.DataFrame(data_dict)
249
250            self._layout.add_plot(plot_index, data_frame, title, test=test)
251            plot_index += 1
252
253        self._layout.finish()
254
255    def _plot_concat(self):
256        """Plot all lines on a single figure"""
257
258        pivot_vals, _ = self.c_mgr.generate_pivots()
259        plot_index = 0
260
261        self._layout = ILinePlotGen(len(self.c_mgr), **self._attr)
262
263        for constraint in self.c_mgr:
264            result = constraint.result
265            title = str(constraint)
266            data_dict = OrderedDict()
267
268            for pivot in pivot_vals:
269                if pivot in result:
270                    if pivot == AttrConf.PIVOT_VAL:
271                        key = ",".join(self._attr["column"])
272                    else:
273                        key = "{0}: {1}".format(self._attr["pivot"], self._attr["map_label"].get(pivot, pivot))
274
275                    data_dict[key] = result[pivot]
276
277            if len(data_dict) > 1:
278                data_frame = self._fix_indexes(data_dict)
279            else:
280                data_frame = pd.DataFrame(data_dict)
281
282            self._layout.add_plot(plot_index, data_frame, title)
283            plot_index += 1
284
285        self._layout.finish()
286
287    def _fix_indexes(self, data_dict):
288        """
289        In case of multiple traces with different indexes (i.e. x-axis values),
290        create new ones with same indexes
291        """
292        # 1) Check if we are processing multiple traces
293        if len(data_dict) <= 1:
294            raise ValueError("Cannot fix indexes for single trace. "\
295                             "Expecting multiple traces!")
296
297        # 2) Merge the data frames to obtain common indexes
298        df_columns = list(data_dict.keys())
299        dedup_data = [handle_duplicate_index(s) for s in data_dict.values()]
300        ret = pd.Series(dedup_data, index=df_columns)
301        merged_df = pd.concat(ret.get_values(), axis=1)
302        merged_df.columns = df_columns
303        # 3) Fill NaN values depending on drawstyle
304        if self._attr["drawstyle"] == "steps-post":
305            merged_df = merged_df.ffill()
306        elif self._attr["drawstyle"] == "steps-pre":
307            merged_df = merged_df.bfill()
308        elif self._attr["drawstyle"] == "steps-mid":
309            merged_df = merged_df.ffill()
310        else:
311            # default
312            merged_df = merged_df.interpolate()
313
314        return merged_df
315