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

cisagov / pe-reports / 5892227966

17 Aug 2023 02:27PM UTC coverage: 33.736% (+7.0%) from 26.737%
5892227966

Pull #565

github

web-flow
Merge 9adf19bbe into 998fa208f
Pull Request #565: Update report generator to use reportlab

93 of 477 branches covered (19.5%)

Branch coverage included in aggregate %.

443 of 1022 new or added lines in 8 files covered. (43.35%)

18 existing lines in 5 files now uncovered.

801 of 2173 relevant lines covered (36.86%)

1.83 hits per line

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

8.88
/src/pe_reports/charts.py
1
"""Class methods for report charts."""
2

3
# Standard Python Libraries
4
import os
5✔
5

6
# Third-Party Libraries
7
import matplotlib
5✔
8
import matplotlib.pyplot as plt
5✔
9
from matplotlib.ticker import MaxNLocator
5✔
10

11
matplotlib.use("Agg")
5✔
12

13

14
# Factor to convert cm to inches
15
CM_CONVERSION_FACTOR = 2.54
5✔
16

17
# Get base directory to save images
18
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
5✔
19

20

21
class Charts:
5✔
22
    """Build charts."""
23

24
    def __init__(self, df, width, height, name, title, x_label, y_label):
5✔
25
        """Initialize chart class."""
26
        self.df = df
×
27
        self.title = title
×
28
        self.x_label = x_label
×
29
        self.y_label = y_label
×
30
        self.width = width
×
31
        self.height = height
×
32
        self.name = name
×
33

34
    def pie(self):
5✔
35
        """Build pie chart."""
36
        df = self.df
×
37
        width = self.width
×
38
        height = self.height
×
39
        name = self.name
×
40
        plt.rcParams.update({"figure.max_open_warning": 0})
×
41
        category_name = df.columns[0]
×
42
        value_name = df.columns[1]
×
43
        df = df.sort_values(by=value_name, ascending=False)
×
44
        category_column = df[category_name]
×
45
        value_column = df[df.columns[1]]
×
46
        labels = category_column
×
47
        plt.gca().axis("equal")
×
48

49
        def autopct(pct):
×
50
            """Get percentages for the pie chart slices > 10%."""
51
            return ("%1.0f%%" % pct) if pct > 1 else ""
×
52

53
        pie = plt.pie(
×
54
            value_column,
55
            startangle=0,
56
            radius=1,
57
            autopct=autopct,
58
            textprops={"color": "w", "fontsize": 7},
59
        )
60
        plt.legend(
×
61
            pie[0],
62
            labels,
63
            bbox_to_anchor=(1, 0.5),
64
            loc="center right",
65
            fontsize=7,
66
            bbox_transform=plt.gcf().transFigure,
67
            frameon=False,
68
        )
69
        plt.subplots_adjust(left=0.2, wspace=0.2)
×
70
        plt.gcf().set_size_inches(
×
71
            width / CM_CONVERSION_FACTOR, height / CM_CONVERSION_FACTOR
72
        )
73
        plt.savefig(
×
74
            BASE_DIR + "/assets/" + name, transparent=True, dpi=500, bbox_inches="tight"
75
        )
76
        plt.clf()
×
77

78
    def stacked_bar(self):
5✔
79
        """Build stacked bar chart."""
80
        df = self.df
×
81
        title = self.title
×
82
        x_label = self.x_label
×
83
        y_label = self.y_label
×
84
        width = self.width
×
85
        height = self.height
×
86
        name = self.name
×
87
        color = ["#1357BE", "#D0342C"]
×
88
        df.plot(kind="bar", stacked=True, zorder=3, color=color)
×
89
        # Add title to chart
90
        plt.title(title, pad=15, fontsize=10)
×
91
        # Format chart's axis
92
        plt.xlabel(x_label, labelpad=10, fontdict={"size": 8})
×
93
        plt.ylabel(y_label, labelpad=10, fontdict={"size": 8})
×
94
        plt.gca().yaxis.set_major_locator(MaxNLocator(integer=True))
×
95
        plt.rc("axes", axisbelow=True)
×
96
        plt.grid(axis="y", zorder=0)
×
97
        plt.xticks(rotation=0)
×
98
        plt.ylim(ymin=0)
×
99
        # Set sizing for image
100
        plt.gcf().set_size_inches(
×
101
            width / CM_CONVERSION_FACTOR, height / CM_CONVERSION_FACTOR
102
        )
103
        plt.tight_layout()
×
104
        # Save chart to assets directory
105
        plt.savefig(BASE_DIR + "/assets/" + name, transparent=True, dpi=500)
×
106
        plt.clf()
×
107

108
    def h_bar(self):
5✔
109
        """Build horizontal bar chart."""
110
        df = self.df
×
111
        x_label = self.x_label
×
112
        y_label = self.y_label
×
113
        width = self.width
×
114
        height = self.height
×
115
        name = self.name
×
116
        plt.rcParams.update({"figure.max_open_warning": 0})
×
117
        category_name = df.columns[0]
×
118
        value_name = df.columns[1]
×
119
        category_column = df[category_name].str.replace("Vulnerable Product - ", "")
×
120
        value_column = df[df.columns[1]]
×
121
        bar_width = 0.6
×
122
        fig, ax = plt.subplots()
×
123
        ax.spines.right.set_visible(False)
×
124
        ax.spines.top.set_visible(False)
×
125
        # Generate horizontal bar chart
126
        plt.barh(df.index, value_column, bar_width, align="center", color="#466fc6")
×
127
        # Specify axis atributes
128
        plt.xticks(fontsize=7)
×
129
        plt.yticks(fontsize=7)
×
130
        plt.xlim(xmin=0)
×
131
        plt.gca().xaxis.set_major_locator(MaxNLocator(integer=True))
×
132
        plt.gca().set_ylim(-1.0, len(category_column))
×
133
        plt.gca().set_yticks(df.index)
×
134
        plt.gca().set_yticklabels(category_column)
×
135
        plt.gca().set_xlabel(x_label, fontdict={"size": 8})
×
136
        plt.gca().set_ylabel(y_label)
×
137
        # Set sizing for image
138
        plt.gcf().set_size_inches(
×
139
            width / CM_CONVERSION_FACTOR, height / CM_CONVERSION_FACTOR
140
        )
141
        plt.tight_layout()
×
142
        # Add data labels to each bar if greater than 0
143
        for i in range(len(df)):
×
144
            if df.loc[i, value_name] > 0:
×
145
                label = df.loc[i, value_name]
×
146
                plt.annotate(
×
147
                    label,  # this is the text
148
                    (df.loc[i, value_name], i),  # this is the point to label
149
                    textcoords="offset points",  # how to position the text
150
                    xytext=(7, -3),  # distance from text to points (x,y)
151
                    ha="center",  # horizontal alignment can be left, right or center
152
                    fontsize=8,
153
                )
154
        # Save chart to assets directory
155
        plt.savefig(
×
156
            BASE_DIR + "/assets/" + name, transparent=True, dpi=500, bbox_inches="tight"
157
        )
158
        plt.clf()
×
159

160
    def line_chart(self):
5✔
161
        """Build line chart."""
162
        df = self.df
×
163
        x_label = self.x_label
×
164
        y_label = self.y_label
×
165
        width = self.width
×
166
        height = self.height
×
167
        name = self.name
×
NEW
168
        color = ["#7aa5c1", "#e08493"]
×
169
        fig, ax = plt.subplots()
×
170
        ax.spines["right"].set_visible(False)
×
171
        ax.spines["top"].set_visible(False)
×
172
        plt.set_loglevel("WARNING")
×
173
        # Plot first line on chart
NEW
174
        plt.plot(
×
175
            df.index,
176
            df[df.columns[0]],
177
            color=color[0],
178
            label=df.columns[0],
179
            linewidth=3,
180
            marker=".",
181
            markersize=10,
182
        )
183
        # If there is another column chart the second line
NEW
184
        if len(df.columns) == 2:
×
UNCOV
185
            plt.plot(
×
186
                df.index,
187
                df[df.columns[1]],
188
                color=color[1],
189
                label=df.columns[1],
190
                linewidth=3,
191
                linestyle="dashed",
192
                marker=".",
193
                markersize=10,
194
            )
195
        # Set the y-max to 110% of the max y value
NEW
196
        y_max = int(df[df.columns].max().max() * 1.1)
×
NEW
197
        plt.ylim(ymin=0, ymax=y_max * 1.10)
×
198
        # Place the legend in the upper right corner
NEW
199
        plt.legend(loc="upper right")
×
200
        # Set size of the chart
NEW
201
        plt.gcf().set_size_inches(
×
202
            width / CM_CONVERSION_FACTOR, height / CM_CONVERSION_FACTOR
203
        )
204
        # Format tick marks and grid layout
205
        plt.xticks(fontsize=7)
×
206
        plt.yticks(fontsize=7)
×
207
        plt.gca().set_ylabel(y_label, labelpad=10, fontdict={"size": 8})
×
208
        plt.xlabel(x_label, labelpad=10, fontdict={"size": 8})
×
209
        plt.xticks(rotation=0)
×
210
        plt.grid(axis="y")
×
UNCOV
211
        plt.tight_layout()
×
212

213
        # Add data labels
214
        # Loop through the dataframe
NEW
215
        for row in df.itertuples():
×
216
            # Check if there is only one row of values
NEW
217
            if len(row) == 2:
×
NEW
218
                plt.annotate(
×
219
                    str(int(row[1])),
220
                    xy=(row[0], row[1]),
221
                    textcoords="offset points",  # Set the manner to position the text
222
                    xytext=(
223
                        0,
224
                        8,
225
                    ),  # Distance from text to points (x,y)
226
                    ha="center",  # Set horizontal alignment to center
227
                    color="#003e67",
228
                )
229
                # Check if there are two rows of data
NEW
230
            elif len(row) == 3:
×
231
                # Check if the two values are within 1/10th of the max y value
NEW
232
                value_diff = abs(row[1] - row[2])
×
NEW
233
                if value_diff < y_max / 10:
×
234
                    # If the values are on the bottom quarter of the graph don't label below values
NEW
235
                    if min(row[1], row[2]) < y_max / 4:
×
NEW
236
                        y1 = y2 = max(row[1], row[2])
×
NEW
237
                        if row[1] > row[2]:
×
NEW
238
                            y1_offset = 18
×
NEW
239
                            y2_offset = 8
×
240
                        else:
NEW
241
                            y1_offset = 8
×
NEW
242
                            y2_offset = 18
×
243
                    # If the values are not in the bottom quarter place the lower value below the point
244
                    else:
NEW
245
                        y1 = row[1]
×
NEW
246
                        y2 = row[2]
×
NEW
247
                        if row[1] > row[2]:
×
NEW
248
                            y1_offset = 8
×
NEW
249
                            y2_offset = -17
×
250
                        else:
NEW
251
                            y1_offset = -17
×
NEW
252
                            y2_offset = 8
×
253
                # If values are not close to each other put the labels directly above the value
254
                else:
NEW
255
                    y1 = row[1]
×
NEW
256
                    y2 = row[2]
×
NEW
257
                    y1_offset = 8
×
NEW
258
                    y2_offset = 8
×
259

260
                # Annotate the data points
NEW
261
                plt.annotate(
×
262
                    str(int(row[1])),
263
                    xy=(row[0], y1),
264
                    textcoords="offset points",  # Set how to position the text
265
                    xytext=(
266
                        0,
267
                        y1_offset,
268
                    ),  # Distance from text to points (x,y)
269
                    ha="center",  # Horizontal alignment can be left, right or center
270
                    color="#005288",
271
                )
NEW
272
                plt.annotate(
×
273
                    str(int(row[2])),
274
                    xy=(row[0], y2),
275
                    textcoords="offset points",  # Set how to position the text
276
                    xytext=(
277
                        0,
278
                        y2_offset,
279
                    ),  # Distance from text to points (x,y)
280
                    ha="center",  # Set horizontal alignment to center
281
                    # fontsize=2,
282
                    color="#c41230",
283
                )
284
        # Save chart to assets directory
285
        plt.savefig(
×
286
            BASE_DIR + "/assets/" + name, transparent=True, dpi=500, bbox_inches="tight"
287
        )
288
        plt.clf()
×
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

© 2025 Coveralls, Inc