• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

spedas / pyspedas / 26171438601

20 May 2026 02:07AM UTC coverage: 90.571% (+0.01%) from 90.557%
26171438601

push

github

jameswilburlewis
Fix crash when a spectrogram is plotted with Y axis interpolation is enabled and Y scaling is linear.
Add regression test for y_interp bug.

0 of 4 new or added lines in 2 files covered. (0.0%)

17831 existing lines in 383 files now uncovered.

44379 of 48999 relevant lines covered (90.57%)

1.43 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.6
/pyspedas/tplot_tools/MPLPlotter/lineplot.py
1
import numpy as np
2✔
2
import pyspedas
2✔
3
import logging
2✔
4

5

6
def lineplot(var_data,
2✔
7
             var_times,
8
             this_axis,
9
             line_opts,
10
             yaxis_options,
11
             plot_extras,
12
             running_trace_count=None,
13
             time_idxs=None,
14
             style=None,
15
             var_metadata=None):
16
    """
17
    Generate a matplotlib line plot from a tplot variable
18

19
    Parameters
20
    ----------
21
        var_data: dict
22
            The data to be plotted (may have multiple traces)
23
        var_times:
24
            Array of datetime objects to use for x axis
25
        this_axis:
26
            The current axis (plot panel) we're working with
27
        line_opts: dict
28
            A dictionary of line options
29
        yaxis_options: dict
30
            A dictionary of y axis options
31
        plot_extras: dict
32
            A dictionary of 'extra' options (colors, etc)
33
        running_trace_count:
34
            If not Null, an integer representing the number of traces already processed in this pseudovariable. Defaults to None.
35
        time_idxs: np.ndarray
36
            If provided, an integer array specifying the subset of time indices to be plotted. Defaults to None.
37
        style:
38
            A matplotlib style to be used in the plot. Defaults to None.
39
        var_metadata: dict
40
            The metadata dictionary associated with this tplot variable (used as a fallback for trace labels). Defaults to None.
41

42
    Returns
43
    -------
44
        True
45

46
    """
47
    alpha = plot_extras.get('alpha')
2✔
48

49
    if len(var_data.y.shape) == 1:
2✔
50
        num_lines = 1
2✔
51
    else:
52
        num_lines = var_data.y.shape[1]
2✔
53

54
    is_errorbar_plot = False
2✔
55
    if 'dy' in var_data._fields:
2✔
UNCOV
56
        is_errorbar_plot = True
1✔
57

58
    if yaxis_options.get('legend_names') is not None:
2✔
59
        labels = yaxis_options['legend_names']
2✔
60
        labels = get_trace_options(labels, running_trace_count, num_lines)
2✔
61

62
        if labels[0] is None:
2✔
63
            labels = None
×
64
    else:
65
        labels = None
2✔
66
        if var_metadata.get('CDF') is not None:
2✔
67
            labels = var_metadata['CDF'].get('LABELS')
2✔
68

69
    legend_location = yaxis_options.get('legend_location')
2✔
70

71
    bbox_to_anchor = None
2✔
72
    if legend_location is not None:
2✔
73
        if legend_location == 'spedas':
2✔
74
            # the spedas legend puts the legend on the outside of the panel
75
            # to the right of the panel (just like in IDL)
76
            legend_location = 'center left'
2✔
77
            bbox_to_anchor = (1.04, 0.5)
2✔
78
    else:
79
        legend_location = 'upper right'
2✔
80

81
    legend_size = yaxis_options.get('legend_size')
2✔
82
    legend_shadow = yaxis_options.get('legend_shadow')
2✔
83
    legend_title = yaxis_options.get('legend_title')
2✔
84
    legend_titlesize = yaxis_options.get('legend_titlesize')
2✔
85
    legend_color = yaxis_options.get('legend_color')
2✔
86
    legend_markerfirst = yaxis_options.get('legend_markerfirst')
2✔
87
    legend_markerscale = yaxis_options.get('legend_markerscale')
2✔
88
    legend_linewidth = yaxis_options.get('legend_linewidth')
2✔
89
    legend_edgecolor = yaxis_options.get('legend_edgecolor')
2✔
90
    legend_facecolor = yaxis_options.get('legend_facecolor')
2✔
91
    legend_frameon = yaxis_options.get('legend_frameon')
2✔
92
    legend_ncols = yaxis_options.get('legend_ncols')
2✔
93
    if legend_ncols is None:
2✔
94
        legend_ncols = 1
2✔
95

96
    if legend_linewidth is None:
2✔
97
        legend_linewidth = 4
2✔
98

99
    if legend_size is None:
2✔
100
        legend_size = pyspedas.tplot_tools.tplot_opt_glob.get('charsize')
2✔
101

102
    markers = None
2✔
103
    if line_opts.get('marker') is not None:
2✔
104
        markers = line_opts['marker']
2✔
105
        markers = get_trace_options(markers, running_trace_count, num_lines, repeat=True)
2✔
106

107
    colors = None
2✔
108
    if plot_extras.get('line_color') is not None:
2✔
109
        colors = plot_extras['line_color']
2✔
110
    else:
111
        if style is None:
2✔
112
            if num_lines == 1:
2✔
113
                colors = ['k']
2✔
114
            elif num_lines == 2:
2✔
UNCOV
115
                colors = ['r', 'g']
1✔
116
            elif num_lines == 3:
2✔
117
                colors = ['b', 'g', 'r']
2✔
118
            elif num_lines == 4:
2✔
119
                colors = ['b', 'g', 'r', 'k']
2✔
120
            else:
121
                colors = ['k', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9']
2✔
122
    colors = get_trace_options(colors, running_trace_count, num_lines, repeat=True)
2✔
123

124
    # line thickness
125
    if line_opts.get('line_width') is not None:
2✔
126
        thick = line_opts['line_width']
2✔
127
    else:
128
        thick = [0.5]
2✔
129
    thick = get_trace_options(thick, running_trace_count, num_lines, repeat=True)
2✔
130

131
    # line style
132
    if line_opts.get('line_style_name') is not None:
2✔
UNCOV
133
        line_style_user = line_opts['line_style_name']
1✔
134

135
        # line_style_user should already be a list
136
        # handle legacy values
UNCOV
137
        line_style = []
1✔
UNCOV
138
        for linestyle in line_style_user:
1✔
UNCOV
139
            if linestyle == 'solid_line':
1✔
140
                line_style.append('solid')
×
UNCOV
141
            elif linestyle == 'dot':
1✔
142
                line_style.append('dotted')
×
UNCOV
143
            elif linestyle == 'dash':
1✔
144
                line_style.append('dashed')
×
UNCOV
145
            elif linestyle == 'dash_dot':
1✔
146
                line_style.append('dashdot')
×
147
            else:
UNCOV
148
                line_style.append(linestyle)
1✔
149
    else:
150
        line_style = ['solid']
2✔
151
    line_style = get_trace_options(line_style, running_trace_count, num_lines, repeat=True)
2✔
152

153
    symbols = False
2✔
154
    if line_opts.get('symbols') is not None:
2✔
155
        if line_opts['symbols']:
2✔
156
            symbols = True
2✔
157

158
    # create the plot
159
    line_options = {'alpha': alpha}
2✔
160

161
    marker_every = None
2✔
162
    if line_opts.get('markevery') is not None:
2✔
UNCOV
163
        marker_every = line_opts['markevery']
1✔
UNCOV
164
        marker_every = get_trace_options(marker_every, running_trace_count, num_lines, repeat=True)
1✔
165

166
    marker_sizes = None
2✔
167
    if line_opts.get('marker_size') is not None:
2✔
UNCOV
168
        marker_sizes = line_opts['marker_size']
1✔
UNCOV
169
        marker_sizes = get_trace_options(marker_sizes, running_trace_count, num_lines, repeat=True)
1✔
170

171
    # check for error data first
172
    if is_errorbar_plot:
2✔
173
        # error data provided
UNCOV
174
        line_options['yerr'] = var_data.dy[time_idxs]
1✔
UNCOV
175
        plotter = this_axis.errorbar
1✔
UNCOV
176
        if line_opts.get('ecolor') is not None:
1✔
UNCOV
177
            line_options['ecolor'] = line_opts['ecolor']
1✔
UNCOV
178
        if line_opts.get('elinewidth') is not None:
1✔
UNCOV
179
            line_options['elinewidth'] = line_opts['elinewidth']
1✔
UNCOV
180
        if line_opts.get('errorevery') is not None:
1✔
UNCOV
181
            line_options['errorevery'] = line_opts['errorevery']
1✔
UNCOV
182
        if line_opts.get('capsize') is not None:
1✔
UNCOV
183
            line_options['capsize'] = line_opts['capsize']
1✔
184
    else:
185
        # no error data provided
186
        plotter = this_axis.plot
2✔
187
        # Note: to turn off connecting lines in an error bar plot, do not use the
188
        # 'symbols' option.  Instead, set the line_options metadata to 'None' (as a string).
189
        if symbols:
2✔
190
            plotter = this_axis.scatter
2✔
191

192
    for line in range(0, num_lines):
2✔
193
        if colors is not None:
2✔
194
            color = colors[line]
2✔
195
        else:
196
            color = None
×
197

198
        if markers is not None:
2✔
199
            marker = markers[line]
2✔
200
        else:
201
            marker = None
2✔
202

203
        if marker_sizes is not None:
2✔
204
            # Note: scaling of marker sizes in scatter plots and line plots is different!
205
            # For line plot and scatter plot marker sizes to match, the line plot
206
            # marker size should be the square root of the scatter plot marker size.
207
            # Maybe that should be enforced here....???
208

UNCOV
209
            if symbols:
1✔
UNCOV
210
                line_options['s'] = marker_sizes[line]
1✔
211
            else:
UNCOV
212
                line_options['markersize'] = marker_sizes[line]
1✔
213

214
        if symbols:
2✔
215
            this_line_style='None'
2✔
216
        else:
217
            this_line_style=line_style[line]
2✔
218

219
        if marker_every is not None:
2✔
UNCOV
220
            line_options['markevery'] = marker_every[line]
1✔
221

222
        this_line = plotter(var_times, var_data.y[time_idxs] if num_lines == 1 else var_data.y[time_idxs, line], color=color,
2✔
223
                            linestyle=this_line_style, linewidth=thick[line], marker=marker, **line_options)
224

225
        if labels is not None:
2✔
226
            try:
2✔
227
                if isinstance(this_line, list):
2✔
228
                    this_line[0].set_label(labels[line])
2✔
229
                else:
230
                    this_line.set_label(labels[line])
2✔
UNCOV
231
            except IndexError:
1✔
UNCOV
232
                continue
1✔
233

234
    if labels is not None:
2✔
235
        legend = this_axis.legend(loc=legend_location, fontsize=legend_size, shadow=legend_shadow, title=legend_title,
2✔
236
                         labelcolor=legend_color, markerfirst=legend_markerfirst, markerscale=legend_markerscale,
237
                         facecolor=legend_facecolor, edgecolor=legend_edgecolor, frameon=legend_frameon, ncols=legend_ncols,
238
                         title_fontsize=legend_titlesize, bbox_to_anchor=bbox_to_anchor)
239
        try:
2✔
240
            handles = legend.legend_handles
2✔
241
        except AttributeError:
×
242
            handles = legend.legendHandles
×
243
        for legobj in handles:
2✔
244
            legobj.set_linewidth(legend_linewidth)
2✔
245

246
    return True
2✔
247

248
def get_trace_options(parent_array, start_trace=None, num_traces=1, repeat=False, fill=False, fillval=None):
2✔
249
    """ Get options for a set of traces from a parent array, extending or slicing as necessary to handle pseudovariable options
250

251
    Parameters
252
    -----------
253
        parent_array: str or list of str
254
            An array of option values to select from
255
        start_trace: int
256
            If set, we are processing a pseuodovariable, and this is the count of line traces processed so far for
257
            previous sub-variables in the current pseuodovariable.
258

259
            If parent_array is long enough (e.g. if set on the pseudovariable and intended to cover the complete
260
            set of traces), we take a slice from start_trace with num_traces entries.  If it matches
261
            the number of traces requested (e.g. parent array comes from this subvariable), we take it as-is.
262
            If there are fewer entries than num_traces (e.g. a single value intended to apply to all traces),
263
            we repeat parent_array or add fill until there are enough values to take a slice from start_trace.
264

265
            If None, we're just processing a regular variable, so we take values starting at zero (extending or filling
266
            as necessary)
267
            Default: None
268
        num_traces: int
269
            The number of traces in the current (sub-)variable.
270
        repeat: bool
271
            If True, extend the parent array by repetition if necessary. Defaults to False.
272
        fill: bool
273
            If True, extend the parent array by adding fill values. Defaults to False.
274
        fillval: Any
275
            If fill=True, values to append to parent_array to make enough entries. Defaults to None.
276

277
    Returns:
278
    --------
279
        list of option values with num_traces entries
280

281
    """
282
    if not isinstance(parent_array,list):
2✔
283
        parent_array=[parent_array]
2✔
284
    parent_length = len(parent_array)
2✔
285
    output_array = parent_array
2✔
286
    if start_trace is not None:
2✔
287
        end_trace = start_trace + num_traces
2✔
288
        if parent_length >= start_trace+num_traces:
2✔
289
            output_array = parent_array[start_trace:end_trace]
2✔
290
        elif parent_length == num_traces:
2✔
291
            output_array = parent_array
2✔
292
        else:
UNCOV
293
            if repeat:
1✔
UNCOV
294
                expansion_factor = int((end_trace/parent_length + 1))
1✔
UNCOV
295
                expanded_array = np.tile(parent_array,expansion_factor)
1✔
UNCOV
296
                output_array = expanded_array[start_trace:end_trace]
1✔
297
            elif fill:
×
298
                output_array = parent_array
×
299
                missing=num_traces-parent_length
×
300
                output_array.extend(np.tile([fillval],missing))
×
301
            else:
302
                logging.warning("Length of trace options (%d) smaller than number of traces (%d)",parent_length, num_traces)
×
303
    else:
304
        if len(parent_array) >= num_traces:
2✔
305
            output_array = parent_array[0:num_traces]
2✔
306
        else:
307
            if repeat:
2✔
308
                expansion_factor = int(num_traces/parent_length + 1)
2✔
309
                expanded_array = np.tile(parent_array,expansion_factor)
2✔
310
                output_array = expanded_array[0:num_traces]
2✔
311
            elif fill:
×
312
                output_array = parent_array
×
313
                missing = num_traces - parent_length
×
314
                output_array.extend(np.tile([fillval], missing))
×
315
            else:
316
                logging.warning("Length of trace options (%d) smaller than number of traces (%d)",parent_length, num_traces)
×
317
    return output_array
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc