diff options
| author | J08nY | 2022-10-19 09:53:01 +0200 |
|---|---|---|
| committer | J08nY | 2022-10-19 09:53:01 +0200 |
| commit | c9930eda9555bf363522e606d1817c13a547b564 (patch) | |
| tree | e1a0085130141c11b97f163018d4793f81a9922e | |
| parent | 3c6bd84a53cd1abc4a6159b0013f84aa0043c254 (diff) | |
| download | sec-certs-c9930eda9555bf363522e606d1817c13a547b564.tar.gz sec-certs-c9930eda9555bf363522e606d1817c13a547b564.tar.zst sec-certs-c9930eda9555bf363522e606d1817c13a547b564.zip | |
Add modified sankey algo to references notebook.
| -rw-r--r-- | notebooks/cc/references.ipynb | 530 |
1 files changed, 450 insertions, 80 deletions
diff --git a/notebooks/cc/references.ipynb b/notebooks/cc/references.ipynb index 489d59f7..71f4135f 100644 --- a/notebooks/cc/references.ipynb +++ b/notebooks/cc/references.ipynb @@ -19,7 +19,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" @@ -73,7 +73,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" @@ -82,12 +82,12 @@ "outputs": [], "source": [ "# Initialize\n", - "dset = CCDataset.from_json(\"../cc_09_10_2022/cc_new.json\")" + "dset = CCDataset.from_json(\"../cc_09_10_2022/cc.json\")" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -118,25 +118,14 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" }, "scrolled": true }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\\newcommand{\\numCcAllDirectReferencing}{1497}\n", - "\\newcommand{\\numCcAllNotDirectReferencing}{3632}\n", - "\\newcommand{\\numCcWithIdDirectReferencing}{1497}\n", - "\\newcommand{\\numCcWithIdNotDirectReferencing}{3556}\n" - ] - } - ], + "outputs": [], "source": [ "df[\"has_outgoing_direct_references\"] = df.directly_referencing.notnull()\n", "df[\"has_incoming_direct_references\"] = df.directly_referenced_by.notnull()\n", @@ -161,18 +150,9 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\\newcommand{\\numCCActiveDirectReferencing}{545}\n", - "\\newcommand{\\numCCActiveDirectReferencingArchived}{165}\n" - ] - } - ], + "outputs": [], "source": [ "print(f\"\\\\newcommand{{\\\\numCCActiveDirectReferencing}}{{{df_id_rich.loc[df_id_rich.status == 'active'].has_outgoing_direct_references.sum()}}}\")\n", "\n", @@ -194,7 +174,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" @@ -221,24 +201,13 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\\newcommand{\\numCCDirectRefsSameCategory}{2123}\n", - "\\newcommand{\\numCCDirectRefsOtherCategory}{192}\n", - "\\newcommand{\\numCCDirectRefs}{2315}\n", - "\\newcommand{\\numCCDirectRefsFromSmartcards}{1886}\n" - ] - } - ], + "outputs": [], "source": [ "cert_id_to_category_mapping = dict(zip(df.cert_id, df.category))\n", "cert_id_to_category_mapping[np.NaN] = \"No references\"\n", @@ -278,7 +247,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" @@ -312,21 +281,13 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Number of certificates that reference some archived certificate: 929\n" - ] - } - ], + "outputs": [], "source": [ "def references_archived_cert(references):\n", " if pd.isnull(references):\n", @@ -365,7 +326,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" @@ -386,7 +347,7 @@ "color_dict = dict(zip(all_schemes, colors))\n", "\n", "figure, axes = plt.subplots(1, 1)\n", - "figure.set_size_inches(4, 5)\n", + "figure.set_size_inches(4, 4)\n", "figure.set_tight_layout(True)\n", "\n", "sankey(exploded.scheme, exploded.ref_scheme, colorDict=color_dict, leftLabels=list(exploded.scheme.unique()), rightLabels=list(exploded.ref_scheme.unique()), fontsize=7, ax=axes)\n", @@ -398,18 +359,9 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\\newcommand{\\numCCUSReferencing}{4}\n", - "\\newcommand{\\numCCUS}{959}\n" - ] - } - ], + "outputs": [], "source": [ "print(f\"\\\\newcommand{{\\\\numCCUSReferencing}}{{{len(df_id_rich.loc[(df_id_rich.scheme == 'US') & (df_id_rich.directly_referencing.notnull())])}}}\")\n", "print(f\"\\\\newcommand{{\\\\numCCUS}}{{{len(df_id_rich.loc[(df_id_rich.scheme == 'US')])}}}\")" @@ -426,7 +378,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" @@ -451,7 +403,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" @@ -471,22 +423,13 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "metadata": { "pycharm": { "name": "#%%\n" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Certificates in dataset: 5129\n", - "Certificates with extracted IDs: 4979\n" - ] - } - ], + "outputs": [], "source": [ "certs_with_ids = {cert.heuristics.cert_id: cert for cert in dset if cert.heuristics.cert_id}\n", "\n", @@ -788,6 +731,433 @@ "execution_count": null, "metadata": {}, "outputs": [], + "source": [ + "import logging\n", + "import warnings\n", + "from collections import defaultdict\n", + "from typing import Any, Dict, List, Optional, Set, Tuple, Union\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "import seaborn as sns\n", + "from numpy import float64, ndarray\n", + "from pandas.core.frame import DataFrame\n", + "from pandas.core.series import Series\n", + "\n", + "class PySankeyException(Exception):\n", + " \"\"\"Generic PySankey Exception.\"\"\"\n", + "\n", + "\n", + "class NullsInFrame(PySankeyException):\n", + " pass\n", + "\n", + "\n", + "class LabelMismatch(PySankeyException):\n", + " pass\n", + "\n", + "LOGGER = logging.getLogger(__name__)\n", + "\n", + "\n", + "def check_data_matches_labels(\n", + " labels: Union[List[str], Set[str]], data: Series, side: str\n", + ") -> None:\n", + " \"\"\"Check whether data matches labels.\n", + " Raise a LabelMismatch Exception if not.\"\"\"\n", + " if len(labels) > 0:\n", + " if isinstance(data, list):\n", + " data = set(data)\n", + " if isinstance(data, pd.Series):\n", + " data = set(data.unique().tolist())\n", + " if isinstance(labels, list):\n", + " labels = set(labels)\n", + " if labels != data:\n", + " msg = \"\\n\"\n", + " if len(labels) <= 20:\n", + " msg = \"Labels: \" + \",\".join(labels) + \"\\n\"\n", + " if len(data) < 20:\n", + " msg += \"Data: \" + \",\".join(data)\n", + " raise LabelMismatch(f\"{side} labels and data do not match.{msg}\")\n", + "\n", + "\n", + "def sankey(\n", + " left: Union[List, ndarray, Series],\n", + " right: Union[ndarray, Series],\n", + " leftWeight: Optional[ndarray] = None,\n", + " rightWeight: Optional[ndarray] = None,\n", + " colorDict: Optional[Dict[str, str]] = None,\n", + " leftLabels: Optional[List[str]] = None,\n", + " rightLabels: Optional[List[str]] = None,\n", + " aspect: int = 4,\n", + " rightColor: bool = False,\n", + " fontsize: int = 14,\n", + " figureName: Optional[str] = None,\n", + " closePlot: bool = False,\n", + " figSize: Optional[Tuple[int, int]] = None,\n", + " ax: Optional[Any] = None,\n", + ") -> Any:\n", + " \"\"\"\n", + " Make Sankey Diagram showing flow from left-->right\n", + " Inputs:\n", + " left = NumPy array of object labels on the left of the diagram\n", + " right = NumPy array of corresponding labels on the right of the diagram\n", + " len(right) == len(left)\n", + " leftWeight = NumPy array of weights for each strip starting from the\n", + " left of the diagram, if not specified 1 is assigned\n", + " rightWeight = NumPy array of weights for each strip starting from the\n", + " right of the diagram, if not specified the corresponding leftWeight\n", + " is assigned\n", + " colorDict = Dictionary of colors to use for each label\n", + " {'label':'color'}\n", + " leftLabels = order of the left labels in the diagram\n", + " rightLabels = order of the right labels in the diagram\n", + " aspect = vertical extent of the diagram in units of horizontal extent\n", + " rightColor = If true, each strip in the diagram will be be colored\n", + " according to its left label\n", + " figSize = tuple setting the width and height of the sankey diagram.\n", + " Defaults to current figure size\n", + " ax = optional, matplotlib axes to plot on, otherwise uses current axes.\n", + " Output:\n", + " ax : matplotlib Axes\n", + " \"\"\"\n", + " ax, leftLabels, leftWeight, rightLabels, rightWeight = init_values(\n", + " ax,\n", + " closePlot,\n", + " figSize,\n", + " figureName,\n", + " left,\n", + " leftLabels,\n", + " leftWeight,\n", + " rightLabels,\n", + " rightWeight,\n", + " )\n", + " plt.rc(\"text\", usetex=False)\n", + " plt.rc(\"font\", family=\"serif\")\n", + " data_frame = _create_dataframe(left, leftWeight, right, rightWeight)\n", + " # Identify all labels that appear 'left' or 'right'\n", + " all_labels = pd.Series(\n", + " np.r_[data_frame.left.unique(), data_frame.right.unique()]\n", + " ).unique()\n", + " LOGGER.debug(\"Labels to handle : %s\", all_labels)\n", + " leftLabels, rightLabels = identify_labels(data_frame, leftLabels, rightLabels)\n", + " colorDict = create_colors(all_labels, colorDict) # type: ignore\n", + " ns_l, ns_r = determine_widths(data_frame, leftLabels, rightLabels)\n", + " # Determine positions of left label patches and total widths\n", + " leftWidths, topEdge = _get_positions_and_total_widths(\n", + " data_frame, leftLabels, \"left\"\n", + " )\n", + " # Determine positions of right label patches and total widths\n", + " rightWidths, topEdge = _get_positions_and_total_widths(\n", + " data_frame, rightLabels, \"right\"\n", + " )\n", + " # Total vertical extent of diagram\n", + " xMax = topEdge / aspect\n", + " draw_vertical_bars(\n", + " ax,\n", + " colorDict, # type: ignore\n", + " fontsize,\n", + " leftLabels,\n", + " leftWidths,\n", + " rightLabels,\n", + " rightWidths,\n", + " xMax, # type: ignore\n", + " )\n", + " plot_strips(\n", + " ax,\n", + " colorDict, # type: ignore\n", + " data_frame,\n", + " leftLabels,\n", + " leftWidths,\n", + " ns_l,\n", + " ns_r,\n", + " rightColor,\n", + " rightLabels,\n", + " rightWidths,\n", + " xMax,\n", + " )\n", + " if figSize is not None:\n", + " plt.gcf().set_size_inches(figSize)\n", + " save_image(figureName)\n", + " if closePlot:\n", + " plt.close()\n", + " return ax\n", + "\n", + "\n", + "def save_image(figureName: Optional[str]) -> None:\n", + " if figureName is not None:\n", + " file_name = f\"{figureName}.png\"\n", + " plt.savefig(file_name, bbox_inches=\"tight\", dpi=150)\n", + " LOGGER.info(\"Sankey diagram generated in '%s'\", file_name)\n", + "\n", + "\n", + "def identify_labels(\n", + " dataFrame: DataFrame, leftLabels: List[str], rightLabels: List[str]\n", + ") -> Tuple[ndarray, ndarray]:\n", + " # Identify left labels\n", + " if len(leftLabels) == 0:\n", + " leftLabels = pd.Series(dataFrame.left.unique()).unique()\n", + " else:\n", + " check_data_matches_labels(leftLabels, dataFrame[\"left\"], \"left\")\n", + " # Identify right labels\n", + " if len(rightLabels) == 0:\n", + " rightLabels = pd.Series(dataFrame.right.unique()).unique()\n", + " else:\n", + " check_data_matches_labels(rightLabels, dataFrame[\"right\"], \"right\")\n", + " return leftLabels, rightLabels\n", + "\n", + "\n", + "def init_values(\n", + " ax: Optional[Any],\n", + " closePlot: bool,\n", + " figSize: Optional[Tuple[int, int]],\n", + " figureName: Optional[str],\n", + " left: Union[List, ndarray, Series],\n", + " leftLabels: Optional[List[str]],\n", + " leftWeight: Optional[ndarray],\n", + " rightLabels: Optional[List[str]],\n", + " rightWeight: Optional[ndarray],\n", + ") -> Tuple[Any, List[str], ndarray, List[str], ndarray]:\n", + " deprecation_warnings(closePlot, figSize, figureName)\n", + " if ax is None:\n", + " ax = plt.gca()\n", + " if leftWeight is None:\n", + " leftWeight = []\n", + " if rightWeight is None:\n", + " rightWeight = []\n", + " if leftLabels is None:\n", + " leftLabels = []\n", + " if rightLabels is None:\n", + " rightLabels = []\n", + " # Check weights\n", + " if len(leftWeight) == 0:\n", + " leftWeight = np.ones(len(left))\n", + " if len(rightWeight) == 0:\n", + " rightWeight = leftWeight\n", + " return ax, leftLabels, leftWeight, rightLabels, rightWeight\n", + "\n", + "\n", + "def deprecation_warnings(\n", + " closePlot: bool, figSize: Optional[Tuple[int, int]], figureName: Optional[str]\n", + ") -> None:\n", + " warn = []\n", + " if figureName is not None:\n", + " msg = \"use of figureName in sankey() is deprecated\"\n", + " warnings.warn(msg, DeprecationWarning)\n", + " warn.append(msg[7:-14])\n", + " if closePlot is not False:\n", + " msg = \"use of closePlot in sankey() is deprecated\"\n", + " warnings.warn(msg, DeprecationWarning)\n", + " warn.append(msg[7:-14])\n", + " if figSize is not None:\n", + " msg = \"use of figSize in sankey() is deprecated\"\n", + " warnings.warn(msg, DeprecationWarning)\n", + " warn.append(msg[7:-14])\n", + " if warn:\n", + " LOGGER.warning(\n", + " \" The following arguments are deprecated and should be removed: %s\",\n", + " \", \".join(warn),\n", + " )\n", + "\n", + "\n", + "def determine_widths(\n", + " dataFrame: DataFrame, leftLabels: ndarray, rightLabels: ndarray\n", + ") -> Tuple[Dict, Dict]:\n", + " # Determine widths of individual strips\n", + " ns_l: Dict = defaultdict()\n", + " ns_r: Dict = defaultdict()\n", + " for leftLabel in leftLabels:\n", + " left_dict = {}\n", + " right_dict = {}\n", + " for rightLabel in rightLabels:\n", + " left_dict[rightLabel] = dataFrame[\n", + " (dataFrame.left == leftLabel) & (dataFrame.right == rightLabel)\n", + " ].leftWeight.sum()\n", + " right_dict[rightLabel] = dataFrame[\n", + " (dataFrame.left == leftLabel) & (dataFrame.right == rightLabel)\n", + " ].rightWeight.sum()\n", + " ns_l[leftLabel] = left_dict\n", + " ns_r[leftLabel] = right_dict\n", + " return ns_l, ns_r\n", + "\n", + "\n", + "def draw_vertical_bars(\n", + " ax: Any,\n", + " colorDict: Union[Dict[str, Tuple[float, float, float]], Dict[str, str]],\n", + " fontsize: int,\n", + " leftLabels: ndarray,\n", + " leftWidths: Dict,\n", + " rightLabels: ndarray,\n", + " rightWidths: Dict,\n", + " xMax: float64,\n", + ") -> None:\n", + " # Draw vertical bars on left and right of each label's section & print label\n", + " for leftLabel in leftLabels:\n", + " ax.fill_between(\n", + " [-0.02 * xMax, 0],\n", + " 2 * [leftWidths[leftLabel][\"bottom\"]],\n", + " 2 * [leftWidths[leftLabel][\"bottom\"] + leftWidths[leftLabel][\"left\"]],\n", + " color=colorDict[leftLabel],\n", + " alpha=0.99,\n", + " )\n", + " ax.text(\n", + " -0.05 * xMax,\n", + " leftWidths[leftLabel][\"bottom\"] + 0.5 * leftWidths[leftLabel][\"left\"],\n", + " leftLabel,\n", + " {\"ha\": \"right\", \"va\": \"center\"},\n", + " fontsize=fontsize,\n", + " )\n", + " for rightLabel in rightLabels:\n", + " ax.fill_between(\n", + " [xMax, 1.02 * xMax],\n", + " 2 * [rightWidths[rightLabel][\"bottom\"]],\n", + " 2 * [rightWidths[rightLabel][\"bottom\"] + rightWidths[rightLabel][\"right\"]],\n", + " color=colorDict[rightLabel],\n", + " alpha=0.99,\n", + " )\n", + " ax.text(\n", + " 1.05 * xMax,\n", + " rightWidths[rightLabel][\"bottom\"] + 0.5 * rightWidths[rightLabel][\"right\"],\n", + " rightLabel,\n", + " {\"ha\": \"left\", \"va\": \"center\"},\n", + " fontsize=fontsize,\n", + " )\n", + "\n", + "\n", + "def create_colors(\n", + " allLabels: ndarray, colorDict: Optional[Dict[str, str]]\n", + ") -> Union[Dict[str, Tuple[float, float, float]], Dict[str, str]]:\n", + " # If no colorDict given, make one\n", + " if colorDict is None:\n", + " colorDict = {}\n", + " palette = \"hls\"\n", + " colorPalette = sns.color_palette(palette, len(allLabels))\n", + " for i, label in enumerate(allLabels):\n", + " colorDict[label] = colorPalette[i]\n", + " else:\n", + " missing = [label for label in allLabels if label not in colorDict.keys()]\n", + " if missing:\n", + " raise ValueError(\n", + " \"The colorDict parameter is missing values for the following labels : \"\n", + " + \", \".join(missing)\n", + " )\n", + " LOGGER.debug(\"The colordict value are : %s\", colorDict)\n", + " return colorDict\n", + "\n", + "\n", + "def _create_dataframe(\n", + " left: Union[List, ndarray, Series],\n", + " leftWeight: Union[ndarray, Series],\n", + " right: Union[ndarray, Series],\n", + " rightWeight: Union[ndarray, Series],\n", + ") -> DataFrame:\n", + " # Create Dataframe\n", + " if isinstance(left, pd.Series):\n", + " left = left.reset_index(drop=True)\n", + " if isinstance(right, pd.Series):\n", + " right = right.reset_index(drop=True)\n", + " if isinstance(leftWeight, pd.Series):\n", + " leftWeight = leftWeight.reset_index(drop=True)\n", + " if isinstance(rightWeight, pd.Series):\n", + " rightWeight = rightWeight.reset_index(drop=True)\n", + " data_frame = pd.DataFrame(\n", + " {\n", + " \"left\": left,\n", + " \"right\": right,\n", + " \"leftWeight\": leftWeight,\n", + " \"rightWeight\": rightWeight,\n", + " },\n", + " index=range(len(left)),\n", + " )\n", + " if len(data_frame[(data_frame.left.isnull()) | (data_frame.right.isnull())]):\n", + " raise NullsInFrame(\"Sankey graph does not support null values.\")\n", + " return data_frame\n", + "\n", + "\n", + "def plot_strips(\n", + " ax: Any,\n", + " colorDict: Union[Dict[str, Tuple[float, float, float]], Dict[str, str]],\n", + " dataFrame: DataFrame,\n", + " leftLabels: ndarray,\n", + " leftWidths: Dict,\n", + " ns_l: Dict,\n", + " ns_r: Dict,\n", + " rightColor: bool,\n", + " rightLabels: ndarray,\n", + " rightWidths: Dict,\n", + " xMax: float64,\n", + ") -> None:\n", + " # Plot strips\n", + " for leftLabel in leftLabels:\n", + " for rightLabel in rightLabels:\n", + " label_color = leftLabel\n", + " if rightColor:\n", + " label_color = rightLabel\n", + " if (\n", + " len(\n", + " dataFrame[\n", + " (dataFrame.left == leftLabel) & (dataFrame.right == rightLabel)\n", + " ]\n", + " )\n", + " > 0\n", + " ):\n", + " # Create array of y values for each strip, half at left value,\n", + " # half at right, convolve\n", + " ys_d = np.array(\n", + " 50 * [leftWidths[leftLabel][\"bottom\"]]\n", + " + 50 * [rightWidths[rightLabel][\"bottom\"]]\n", + " )\n", + " ys_d = np.convolve(ys_d, 0.05 * np.ones(20), mode=\"valid\")\n", + " ys_d = np.convolve(ys_d, 0.05 * np.ones(20), mode=\"valid\")\n", + " ys_u = np.array(\n", + " 50 * [leftWidths[leftLabel][\"bottom\"] + ns_l[leftLabel][rightLabel]]\n", + " + 50\n", + " * [rightWidths[rightLabel][\"bottom\"] + ns_r[leftLabel][rightLabel]]\n", + " )\n", + " ys_u = np.convolve(ys_u, 0.05 * np.ones(20), mode=\"valid\")\n", + " ys_u = np.convolve(ys_u, 0.05 * np.ones(20), mode=\"valid\")\n", + "\n", + " # Update bottom edges at each label so next strip starts at the\n", + " # right place\n", + " leftWidths[leftLabel][\"bottom\"] += ns_l[leftLabel][rightLabel]\n", + " rightWidths[rightLabel][\"bottom\"] += ns_r[leftLabel][rightLabel]\n", + " ax.fill_between(\n", + " np.linspace(0, xMax, len(ys_d)),\n", + " ys_d,\n", + " ys_u,\n", + " alpha=0.65,\n", + " color=colorDict[label_color],\n", + " )\n", + " ax.axis(\"off\")\n", + "\n", + "\n", + "def _get_positions_and_total_widths(\n", + " df: DataFrame, labels: ndarray, side: str\n", + ") -> Tuple[Dict, float64]:\n", + " \"\"\"Determine positions of label patches and total widths\"\"\"\n", + " widths: Dict = defaultdict()\n", + " for i, label in enumerate(labels):\n", + " label_widths = {}\n", + " label_widths[side] = df[df[side] == label][side + \"Weight\"].sum()\n", + " print(\"a\")\n", + " if i == 0:\n", + " label_widths[\"bottom\"] = 0\n", + " label_widths[\"top\"] = label_widths[side]\n", + " else:\n", + " bottom_width = widths[labels[i - 1]][\"top\"]\n", + " weighted_sum = 0.05 * df[side + \"Weight\"].sum()\n", + " label_widths[\"bottom\"] = bottom_width + weighted_sum\n", + " label_widths[\"top\"] = label_widths[\"bottom\"] + label_widths[side]\n", + " topEdge = label_widths[\"top\"]\n", + " widths[label] = label_widths\n", + " LOGGER.debug(\"%s position of '%s' : %s\", side, label, label_widths)\n", + " return widths, topEdge\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [] } ], @@ -812,4 +1182,4 @@ }, "nbformat": 4, "nbformat_minor": 1 -} +}
\ No newline at end of file |
