1# Copyright 2016-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"""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 traces: The input data 35 :type traces: a list of :mod:`trappy.trace.FTrace`, 36 :mod:`trappy.trace.SysTrace`, :mod:`trappy.trace.BareTrace` 37 or :mod:`pandas.DataFrame` or a single instance of them. 38 39 :param column: specifies the name of the column to 40 be plotted. 41 :type column: (str, list(str)) 42 43 :param templates: TRAPpy events 44 45 .. note:: 46 47 This is not required if a :mod:`pandas.DataFrame` is 48 used 49 50 :type templates: :mod:`trappy.base.Base` 51 52 :param filters: Filter the column to be plotted as per the 53 specified criteria. For Example: 54 :: 55 56 filters = 57 { 58 "pid": [ 3338 ], 59 "cpu": [0, 2, 4], 60 } 61 :type filters: dict 62 63 :param per_line: Used to control the number of graphs 64 in each graph subplot row 65 :type per_line: int 66 67 :param concat: Draw all the pivots on a single graph 68 :type concat: bool 69 70 :param permute: Draw one plot for each of the traces specified 71 :type permute: bool 72 73 :param drawstyle: This argument is forwarded to the matplotlib 74 corresponding :func:`matplotlib.pyplot.plot` call 75 76 drawing style. 77 78 .. note:: 79 80 step plots are not currently supported for filled 81 graphs 82 83 :param xlim: A tuple representing the upper and lower xlimits 84 :type xlim: tuple 85 86 :param ylim: A tuple representing the upper and lower ylimits 87 :type ylim: tuple 88 89 :param title: A title describing all the generated plots 90 :type title: str 91 92 :param style: Created pre-styled graphs loaded from 93 :mod:`trappy.plotter.AttrConf.MPL_STYLE` 94 :type style: bool 95 96 :param signals: A string of the type event_name:column 97 to indicate the value that needs to be plotted 98 99 .. note:: 100 101 - Only one of `signals` or both `templates` and 102 `columns` should be specified 103 - Signals format won't work for :mod:`pandas.DataFrame` 104 input 105 106 :type signals: str 107 108 :param legend_ncol: A positive integer that represents the 109 number of columns in the legend 110 :type legend_ncol: int 111 """ 112 __metaclass__ = ABCMeta 113 114 def __init__(self, traces, templates, **kwargs): 115 self._fig = None 116 self._layout = None 117 super(StaticPlot, self).__init__(traces=traces, 118 templates=templates) 119 120 self.set_defaults() 121 122 for key in kwargs: 123 if key in AttrConf.ARGS_TO_FORWARD: 124 self._attr["args_to_forward"][key] = kwargs[key] 125 else: 126 self._attr[key] = kwargs[key] 127 128 if "signals" in self._attr: 129 self._describe_signals() 130 131 self._check_data() 132 133 if "column" not in self._attr: 134 raise RuntimeError("Value Column not specified") 135 136 zip_constraints = not self._attr["permute"] 137 self.c_mgr = ConstraintManager(traces, self._attr["column"], 138 self.templates, self._attr["pivot"], 139 self._attr["filters"], 140 zip_constraints=zip_constraints) 141 142 def savefig(self, *args, **kwargs): 143 """Save the plot as a PNG fill. This calls into 144 :mod:`matplotlib.figure.savefig` 145 """ 146 147 if self._fig is None: 148 self.view() 149 self._fig.savefig(*args, **kwargs) 150 151 @abstractmethod 152 def set_defaults(self): 153 """Sets the default attrs""" 154 self._attr["width"] = AttrConf.WIDTH 155 self._attr["length"] = AttrConf.LENGTH 156 self._attr["per_line"] = AttrConf.PER_LINE 157 self._attr["concat"] = AttrConf.CONCAT 158 self._attr["filters"] = {} 159 self._attr["style"] = True 160 self._attr["permute"] = False 161 self._attr["pivot"] = AttrConf.PIVOT 162 self._attr["xlim"] = AttrConf.XLIM 163 self._attr["ylim"] = AttrConf.YLIM 164 self._attr["title"] = AttrConf.TITLE 165 self._attr["args_to_forward"] = {} 166 self._attr["map_label"] = {} 167 self._attr["_legend_handles"] = [] 168 self._attr["_legend_labels"] = [] 169 self._attr["legend_ncol"] = AttrConf.LEGEND_NCOL 170 171 def view(self, test=False): 172 """Displays the graph""" 173 174 if test: 175 self._attr["style"] = True 176 AttrConf.MPL_STYLE["interactive"] = False 177 178 permute = self._attr["permute"] and not self._attr["concat"] 179 if self._attr["style"]: 180 with plt.rc_context(AttrConf.MPL_STYLE): 181 self._resolve(permute, self._attr["concat"]) 182 else: 183 self._resolve(permute, self._attr["concat"]) 184 185 def make_title(self, constraint, pivot, permute, concat): 186 """Generates a title string for an axis""" 187 if concat: 188 return str(constraint) 189 190 if permute: 191 return constraint.get_data_name() 192 elif pivot != AttrConf.PIVOT_VAL: 193 return "{0}: {1}".format(self._attr["pivot"], self._attr["map_label"].get(pivot, pivot)) 194 else: 195 return "" 196 197 def add_to_legend(self, series_index, handle, constraint, pivot, concat, permute): 198 """ 199 Add series handles and names to the legend 200 A handle is returned from a plot on an axis 201 e.g. Line2D from axis.plot() 202 """ 203 self._attr["_legend_handles"][series_index] = handle 204 legend_labels = self._attr["_legend_labels"] 205 206 if concat and pivot == AttrConf.PIVOT_VAL: 207 legend_labels[series_index] = self._attr["column"] 208 elif concat: 209 legend_labels[series_index] = "{0}: {1}".format( 210 self._attr["pivot"], 211 self._attr["map_label"].get(pivot, pivot) 212 ) 213 elif permute: 214 legend_labels[series_index] = constraint._template.name + ":" + constraint.column 215 else: 216 legend_labels[series_index] = str(constraint) 217 218 def _resolve(self, permute, concat): 219 """Determine what data to plot on which axis""" 220 pivot_vals, len_pivots = self.c_mgr.generate_pivots(permute) 221 pivot_vals = list(pivot_vals) 222 223 num_of_axes = len(self.c_mgr) if concat else len_pivots 224 225 # Create a 2D Layout 226 self._layout = PlotLayout( 227 self._attr["per_line"], 228 num_of_axes, 229 width=self._attr["width"], 230 length=self._attr["length"], 231 title=self._attr['title']) 232 233 self._fig = self._layout.get_fig() 234 235 # Determine what constraint to plot and the corresponding pivot value 236 if permute: 237 legend_len = self.c_mgr._max_len 238 pivots = [y for _, y in pivot_vals] 239 c_dict = {c : str(c) for c in self.c_mgr} 240 c_list = sorted(c_dict.items(), key=lambda x: (x[1].split(":")[-1], x[1].split(":")[0])) 241 constraints = [c[0] for c in c_list] 242 cp_pairs = [(c, p) for c in constraints for p in sorted(set(pivots))] 243 else: 244 legend_len = len_pivots if concat else len(self.c_mgr) 245 pivots = pivot_vals 246 cp_pairs = [(c, p) for c in self.c_mgr for p in pivots if p in c.result] 247 248 # Initialise legend data and colormap 249 self._attr["_legend_handles"] = [None] * legend_len 250 self._attr["_legend_labels"] = [None] * legend_len 251 252 if "colors" in self._attr: 253 self._cmap = ColorMap.rgb_cmap(self._attr["colors"]) 254 else: 255 self._cmap = ColorMap(legend_len) 256 257 # Group constraints/series with the axis they are to be plotted on 258 figure_data = ddict(list) 259 for i, (constraint, pivot) in enumerate(cp_pairs): 260 axis = self._layout.get_axis(constraint.trace_index if concat else i) 261 figure_data[axis].append((constraint, pivot)) 262 263 # Plot each axis 264 for axis, series_list in figure_data.iteritems(): 265 self.plot_axis( 266 axis, 267 series_list, 268 permute, 269 self._attr["concat"], 270 self._attr["args_to_forward"] 271 ) 272 if self._attr["xlim"]: 273 axis.set_xlim(self._attr["xlim"]) 274 if self._attr["ylim"]: 275 axis.set_ylim(self._attr["ylim"]) 276 277 # Show legend 278 legend = self._fig.legend(self._attr["_legend_handles"], 279 self._attr["_legend_labels"], 280 loc='lower center', 281 ncol=self._attr["legend_ncol"], 282 borderaxespad=0.) 283 legend.get_frame().set_facecolor('#F4F4F4') 284 285 self._layout.finish(num_of_axes) 286 287 def plot_axis(self, axis, series_list, permute, concat, args_to_forward): 288 """Internal Method called to plot data (series_list) on a given axis""" 289 raise NotImplementedError("Method Not Implemented") 290