Skip to content

View

qcdata.view

Tools for visualizing qcdata objects in Jupyter Notebooks.

Design Decisions
  • The view function is the top-level method for viewing all qcdata objects. It can accept one or many objects and will determine the appropriate viewer to use.
  • All functions that begin with "generate" will return a string of HTML. If users want to use this HTML to create a custom view, they can do so. If they want to display the HTML in a Jupyter Notebook, they can call display(HTML(html_string)) after importing from IPython.display import HTML, display.
  • The basic layout for viewing outputs (all ProgramOutput objects) is a table of basic parameters followed by a structure viewer and results table or plot. Nested program specifications add recursive subprogram details. ---------------------------------------------------------------------------- | Structure | Success | Calculation Type | Program | Model | Keywords | ---------------------------------------------------------------------------- | | | | Structure Viewer (Optional) | Data Table or Plot | | | | ----------------------------------------------------------------------------

DEFAULT_WIDTH module-attribute

DEFAULT_WIDTH: int = 600

The default width of the viewer in pixels.

DEFAULT_HEIGHT module-attribute

DEFAULT_HEIGHT: int = 450

The default height of the viewer in pixels.

view

view(
    *objs: ProgramOutput
    | Frame
    | Structure
    | list[Structure],
    **kwargs: Any,
) -> None

Display any qcdata objects in a Jupyter Notebook.

This is the top-level viewing function and should be the only one needed to view any qcdata object.

Parameters:

Name Type Description Default
*objs ProgramOutput | Frame | Structure | list[Structure]

The ProgramOutput, Frame, or Structure objects to view. May pass one or more objects or one or more lists of Structure objects. A Frame (a step of an optimization or a point of a path) shows its structure.

()
**kwargs Any

Additional keyword arguments to pass to the viewer functions.

{}

Returns:

Type Description
None

None. Displays the qcdata objects in the Jupyter Notebook.

Source code in src/qcdata/view.py
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
def view(
    *objs: ProgramOutput | Frame | Structure | list[Structure],
    **kwargs: Any,
) -> None:
    """Display any qcdata objects in a Jupyter Notebook.

    This is the top-level viewing function and should be the only one needed to view
    any qcdata object.

    Args:
        *objs: The ProgramOutput, Frame, or Structure objects to view. May pass one
            or more objects or one or more lists of Structure objects. A Frame (a
            step of an optimization or a point of a path) shows its structure.
        **kwargs: Additional keyword arguments to pass to the viewer functions.

    Returns:
        None. Displays the qcdata objects in the Jupyter Notebook.
    """
    if all([isinstance(o, Structure) for o in objs]) or all(
        isinstance(o, Structure) for lst in objs for o in lst
    ):
        structures(*objs, **kwargs)  # type: ignore
        # Handle all structures in a single viewer
        return

    for obj in objs:
        if isinstance(obj, Structure) or isinstance(obj, list):
            structures(*objs, **kwargs)  # type: ignore

        elif isinstance(obj, ProgramOutput):
            program_outputs(obj, **kwargs)

        elif isinstance(obj, Frame):
            structures(obj.structure, **kwargs)

        else:
            raise NotImplementedError(f"Viewing of {type(obj)} is not implemented.")

program_outputs

program_outputs(
    *results: ProgramOutput[Inputs, Data],
    animate: bool = True,
    struct_viewer: bool = True,
    conformer_rmsd_threshold: float | None = None,
    conformer_rmsd_backend: str = "rdkit",
    conformer_rmsd_kwargs: dict | None = None,
    **kwargs: Any,
) -> None

Display one or many ProgramOutput objects.

Parameters:

Name Type Description Default
*results ProgramOutput[Inputs, Data]

The ProgramOutput objects to display.

()
animate bool

Whether to animate the structure if it is an optimization.

True
struct_viewer bool

Whether to display the structure viewer.

True
conformer_rmsd_threshold float | None

The RMSD threshold in Bohr below which conformers are considered duplicates and hidden. If None (default), all conformers are shown.

None
conformer_rmsd_backend str

The qcinf backend used to compute conformer RMSDs when filtering. Only used if conformer_rmsd_threshold is set.

'rdkit'
conformer_rmsd_kwargs dict | None

Additional keyword arguments to pass to the conformer RMSD filtering function.

None
**kwargs Any

Additional keyword arguments to pass to the viewer functions.

{}

Returns:

Type Description
None

None. Displays the ProgramOutput objects in the Jupyter Notebook.

Source code in src/qcdata/view.py
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
def program_outputs(
    *results: ProgramOutput[Inputs, Data],
    animate: bool = True,
    struct_viewer: bool = True,
    conformer_rmsd_threshold: float | None = None,
    conformer_rmsd_backend: str = "rdkit",
    conformer_rmsd_kwargs: dict | None = None,
    **kwargs: Any,
) -> None:
    """Display one or many ProgramOutput objects.

    Args:
        *results: The ProgramOutput objects to display.
        animate: Whether to animate the structure if it is an optimization.
        struct_viewer: Whether to display the structure viewer.
        conformer_rmsd_threshold: The RMSD threshold in Bohr below which conformers are
            considered duplicates and hidden. If None (default), all conformers are
            shown.
        conformer_rmsd_backend: The qcinf backend used to compute conformer RMSDs
            when filtering. Only used if conformer_rmsd_threshold is set.
        conformer_rmsd_kwargs: Additional keyword arguments to pass to the conformer
            RMSD filtering function.
        **kwargs: Additional keyword arguments to pass to the viewer functions.

    Returns:
        None. Displays the ProgramOutput objects in the Jupyter Notebook.
    """
    width = kwargs.get("width", DEFAULT_WIDTH)
    height = kwargs.get("height", DEFAULT_HEIGHT)

    for i, result in enumerate(results):
        final_html = []
        final_html.append(generate_output_table(result))

        if isinstance(result.results, ConformerSearchData) and isinstance(
            result.input_data, ProgramInput
        ):
            structures = [result.input_data.require_structure()]

            if conformer_rmsd_threshold is not None:
                keep_indices = filter_conformers_indices(
                    result.results.structures,
                    backend=conformer_rmsd_backend,
                    threshold=conformer_rmsd_threshold,
                    **(conformer_rmsd_kwargs or {}),
                )
                conformers = [result.results.structures[i] for i in keep_indices]
                energies_rel = result.results.conformer_energies_relative
                if energies_rel.size:
                    energies_rel = energies_rel[keep_indices]
            else:
                conformers = result.results.structures
                energies_rel = result.results.conformer_energies_relative

            structures += conformers
            titles_extra = ["Initial Structure"] + [
                f"Conformer {i}" for i in range(len(conformers))
            ]
            subtitles = ["Rel Energy: Unknown"] + [
                f"Rel Energy: +{re * constants.HARTREE_TO_KCAL_PER_MOL:.3f} kcal/mol"
                for re in energies_rel
            ]
            conf_viewer = generate_structure_viewer_html(
                *structures, titles_extra=titles_extra, subtitles=subtitles, **kwargs
            )
            final_html.append(conf_viewer)
            display(HTML("".join(final_html)))

        else:
            # Create structure viewer
            if not struct_viewer:
                structure_html = "struct_viewer = False"
            elif not isinstance(result.input_data, ProgramInput):
                structure_html = "No structure available."
            else:
                titles_extra = kwargs.pop("titles_extra", [])
                try:
                    title_extra = titles_extra[i]
                except IndexError:
                    title_extra = ""

                # Determine the Structure to use
                if isinstance(result.results, OptimizationData):
                    for_viewer: Structure | list[Structure]
                    if not result.results.frames:
                        for_viewer = result.input_data.require_structure()
                        title_extra += " (Initial Structure; no optimization steps)"
                    elif animate:
                        for_viewer = result.results.structures
                    else:
                        for_viewer = result.results.structures[-1]
                        title_extra += " (Last Evaluated Structure)"

                elif isinstance(result.results, (PathData, PathOptimizationData)):
                    path = result.results.structures
                    if not path:
                        for_viewer = (
                            result.input_data.structure
                            or result.input_data.initial_chain
                        )
                        title_extra += " (Input; no path computed)"
                    else:
                        for_viewer = path
                        title_extra += " (Path)"

                elif isinstance(result.results, SinglePointData):
                    for_viewer = result.input_data.require_structure()

                elif isinstance(result.results, FileData):
                    for_viewer = result.input_data.require_structure()
                else:
                    raise NotImplementedError(
                        f"Viewing of {type(result.results)} is not yet implemented."
                    )

                structure_html = generate_structure_viewer_html(
                    for_viewer,
                    titles_extra=[title_extra],
                    **kwargs,
                )

            # Create data table or plot
            if isinstance(result.results, OptimizationData):
                data_html = generate_optimization_plot(
                    result, figsize=(width / 100, height / 100)
                )
            else:
                data_html = generate_data_table(result.results)

            final_html.append(
                f"""
        <div style="text-align: center;">
            <div style="display: flex; align-items: center; justify-content: 
                space-around;">
                <div style="text-align: center; margin-right: 20px; flex: 1;">
                    <div style="display: inline-block; text-align: center;">
                        {structure_html}
                    </div>
                </div>
                <div style="width: {width}px; height: {height}px; text-align: center; 
                    margin-left: 20px; flex: 1; overflow: auto;">
                    {data_html}
                </div>
            </div>
        </div>
                """
            )

            display(HTML("".join(final_html)))

structures

structures(
    *structs: Structure | list[Structure], **kwargs: Any
) -> None

Display one or many Structure objects.

Parameters:

Name Type Description Default
*structs Structure | list[Structure]

The Structure objects or list of objects to display. If a list is passed the structures will be animated.

()
**kwargs Any

Additional keyword arguments to pass to the viewer functions.

{}

Returns:

Type Description
None

None. Displays the structures in the Jupyter Notebook.

Source code in src/qcdata/view.py
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
def structures(
    *structs: Structure | list[Structure],
    **kwargs: Any,
) -> None:
    """Display one or many Structure objects.

    Args:
        *structs: The Structure objects or list of objects to display. If a list is
            passed the structures will be animated.
        **kwargs: Additional keyword arguments to pass to the viewer functions.

    Returns:
        None. Displays the structures in the Jupyter Notebook.
    """
    display(HTML(generate_structure_viewer_html(*structs, **kwargs)))

generate_structure_viewer_html

generate_structure_viewer_html(
    *structs: Structure | list[Structure],
    width: int | None = None,
    height: int | None = None,
    titles: list[str] | None = None,
    subtitles: list[str] | None = None,
    titles_extra: list[str] | None = None,
    subtitles_extra: list[str] | None = None,
    distances: list[tuple[int, int]] | None = None,
    length_unit: LengthUnit = BOHR,
    style: dict | None = None,
    show_indices: bool = False,
    same_viewer: bool = False,
    view_2d: bool = False,
    interval: int = 100,
) -> str

Generate the core HTML viewer for one or more Structures.

Structures are rendered in 3D with py3Dmol or as 2D PNG images. These keywords may be passed to high level viewer functions.

Parameters:

Name Type Description Default
structs Structure | list[Structure]

The Structure or list of Structures to visualize.

()
width int | None

The width of the viewer in pixels. Defaults to 600.

None
height int | None

The height of the viewer in pixels. Defaults to 450.

None
titles list[str] | None

The titles to display above the viewer. Will default to the Structure name if not provided.

None
subtitles list[str] | None

The subtitles to display below the viewer.

None
titles_extra list[str] | None

Extra text to display after the title. This is useful for adding additional context to a default title.

None
subtitles_extra list[str] | None

Extra text to display after the subtitle. Useful for adding text after an autogenerated subtitle such as relative energy.

None
distances list[tuple[int, int]] | None

A list of tuples of atom indices to display distances between.

None
length_unit LengthUnit

The units to display the distances in. Defaults to bohr. May pass 'angstrom' to display in angstroms.

BOHR
style dict | None

A dictionary of styles to apply to the viewer (for py3Dmol). Should be a dictionary for AtomStyleSpec https://3dmol.org/doc/AtomStyleSpec.html

None
show_indices bool

Whether to show the atom indices in the viewer.

False
same_viewer bool

Whether to overlay all structures in a single py3Dmol viewer instead of a two-column grid of viewers. Ignored when view_2d is True.

False
view_2d bool

Whether to display the structure in 2D (PNG images).

False
interval int

The interval in milliseconds for the animation.

100

Returns:

Name Type Description
str str

The HTML string for the viewer.

Raises:

Type Description
ValueError

If a list of Structures is passed and view_2d is True.

Source code in src/qcdata/view.py
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
def generate_structure_viewer_html(
    *structs: Structure | list[Structure],
    width: int | None = None,
    height: int | None = None,
    titles: list[str] | None = None,
    subtitles: list[str] | None = None,
    titles_extra: list[str] | None = None,
    subtitles_extra: list[str] | None = None,
    distances: list[tuple[int, int]] | None = None,
    length_unit: LengthUnit = LengthUnit.BOHR,
    style: dict | None = None,
    show_indices: bool = False,
    same_viewer: bool = False,
    view_2d: bool = False,
    interval: int = 100,
) -> str:
    """Generate the core HTML viewer for one or more Structures.

    Structures are rendered in 3D with py3Dmol or as 2D PNG images. These keywords
    may be passed to high level viewer functions.

    Args:
        structs: The Structure or list of Structures to visualize.
        width: The width of the viewer in pixels. Defaults to 600.
        height: The height of the viewer in pixels. Defaults to 450.
        titles: The titles to display above the viewer. Will default to the Structure
            name if not provided.
        subtitles: The subtitles to display below the viewer.
        titles_extra: Extra text to display after the title. This is useful for adding
            additional context to a default title.
        subtitles_extra: Extra text to display after the subtitle. Useful for adding
            text after an autogenerated subtitle such as relative energy.
        distances: A list of tuples of atom indices to display distances between.
        length_unit: The units to display the distances in. Defaults to bohr. May
            pass 'angstrom' to display in angstroms.
        style: A dictionary of styles to apply to the viewer (for py3Dmol). Should be a
            dictionary for AtomStyleSpec <https://3dmol.org/doc/AtomStyleSpec.html>
        show_indices: Whether to show the atom indices in the viewer.
        same_viewer: Whether to overlay all structures in a single py3Dmol viewer
            instead of a two-column grid of viewers. Ignored when view_2d is True.
        view_2d: Whether to display the structure in 2D (PNG images).
        interval: The interval in milliseconds for the animation.

    Returns:
        str: The HTML string for the viewer.

    Raises:
        ValueError: If a list of Structures is passed and view_2d is True.
    """
    # Set defaults
    width = width or DEFAULT_WIDTH
    height = height or DEFAULT_HEIGHT

    titles = titles or []
    subtitles = subtitles or []
    titles_extra = titles_extra or []
    subtitles_extra = subtitles_extra or []

    # Start HTML with flex container
    html_parts = [
        '<div style="display: flex; flex-wrap: wrap; justify-content: center;">'
    ]

    if not view_2d:
        # Create the viewer
        if len(structs) == 1 or same_viewer:
            viewer = p3d.view(width=width, height=height)
        else:
            # Determine the number of rows needed for multiple structures
            rows = math.ceil(len(structs) / 2)
            viewer = p3d.view(
                width=width * 2,
                height=height * rows,
                linked=False,
                viewergrid=(rows, 2),
            )

    for i, (struct, title, subtitle, title_extra, subtitle_extra) in enumerate(
        zip_longest(structs, titles, subtitles, titles_extra, subtitles_extra)
    ):
        # Set the title and subtitle
        if isinstance(struct, list):
            name = struct[0].ids.name
        else:
            name = struct.ids.name

        title = f"{title or name or ''}"
        title_extra = f"{title_extra or ''}"
        subtitle = f"{subtitle or ''}"
        subtitle_extra = f"{subtitle_extra or ''}"

        if view_2d:
            if isinstance(struct, list):
                raise ValueError(
                    "Cannot display multiple 2D structures in one viewer. Do not submit"
                    " Structures in a list. If you want to view multiple structures, "
                    "unpack your list with *my_list_of_structures."
                )
            adjusted_width, adjusted_height = int(width * 0.75), int(height * 0.75)
            mol = Chem.MolFromSmiles(struct.ids.smiles or structure_to_smiles(struct))
            img = Draw.MolToImage(mol, size=(adjusted_width, adjusted_height))
            buf = io.BytesIO()
            img.save(buf, format="PNG")
            img_str = base64.b64encode(buf.getvalue()).decode("utf-8")

            html_parts.append(
                f'<div style="margin: 10px; text-align: center; padding: 15px; width: '
                f'{adjusted_width}px; height: {adjusted_height + 60}px;">'
                f"<h2>{title} {title_extra}</h2>"
                f'<img src="data:image/png;base64,{img_str}" width="{adjusted_width}" '
                f'height="{adjusted_height}"/>'
                f"<h4>{subtitle} {subtitle_extra}</h4>"
                f"</div>"
            )
        else:
            if same_viewer:
                # No grid for single viewer (better performance than (1,1) grid)
                viewer_kwargs = {}
            else:
                # Sets the viewer to the correct grid position
                viewer_kwargs = {"viewer": divmod(i, 2)}

            if isinstance(struct, list):  # Animate lists of structures
                combined_xyz = "".join(s.to_xyz() for s in struct)
                viewer.addModelsAsFrames(combined_xyz, "xyz", **viewer_kwargs)
                viewer.animate(
                    {"loop": "forward", "interval": interval}, **viewer_kwargs
                )
            else:
                viewer.addModel(struct.to_xyz(), "xyz", **viewer_kwargs)

            viewer.addLabel(
                f"{title} {title_extra}",
                {
                    "position": {"x": width / 2, "y": 0, "z": 0},
                    "alignment": "topCenter",
                    "fontSize": 24,
                    "backgroundOpacity": 0,
                    "fontColor": "black",
                    "useScreen": True,
                },
                **viewer_kwargs,
            )

            viewer.addLabel(
                f"{subtitle} {subtitle_extra}",
                {
                    "position": {"x": width / 2, "y": height, "z": 0},
                    "alignment": "bottomCenter",
                    "fontSize": 20,
                    "backgroundOpacity": 0,
                    "fontColor": "black",
                    "useScreen": True,
                },
                **viewer_kwargs,
            )
            if distances:
                assert isinstance(struct, Structure), (
                    "Displaying distances for lists of structures is not yet implemented"
                )
                for atom1, atom2 in distances:
                    a1_coords = struct.geometry_angstrom[atom1]
                    a2_coords = struct.geometry_angstrom[atom2]
                    # Add line between the two atoms
                    viewer.addLine(
                        {
                            "start": {
                                "x": a1_coords[0],
                                "y": a1_coords[1],
                                "z": a1_coords[2],
                            },
                            "end": {
                                "x": a2_coords[0],
                                "y": a2_coords[1],
                                "z": a2_coords[2],
                            },
                            "color": "red",
                            "linewidth": 2,
                        },
                        **viewer_kwargs,
                    )
                    # Add a label showing the distance
                    midpoint = (a1_coords + a2_coords) / 2
                    distance = struct.distance(atom1, atom2, units=length_unit)
                    unit = "a₀"

                    if length_unit == LengthUnit.ANGSTROM:
                        unit = "Å"

                    viewer.addLabel(
                        f"{distance:.2f} {unit}",
                        {
                            "position": {
                                "x": midpoint[0],
                                "y": midpoint[1],
                                "z": midpoint[2],
                            },
                            "backgroundColor": "white",
                            "fontSize": 14,
                            "fontColor": "black",
                        },
                        **viewer_kwargs,
                    )

            if show_indices:
                ang_geom = (
                    struct.geometry_angstrom
                    if not isinstance(struct, list)
                    else struct[0].geometry_angstrom
                )
                for j, coord in enumerate(ang_geom):
                    viewer.addLabel(
                        str(j),
                        {"position": {"x": coord[0], "y": coord[1], "z": coord[2]}},
                        **viewer_kwargs,
                    )

    if not view_2d:
        viewer.setStyle(style or {"stick": {}, "sphere": {"scale": 0.3}})
        viewer.zoomTo()
        html_parts.append(f"{viewer.write_html()}")

    html_parts.append("</div>")
    return "".join(html_parts)

generate_output_table

generate_output_table(*results: ProgramOutput) -> str

Generate an HTML table displaying the basic parameters for ProgramOutput objects.

Parameters:

Name Type Description Default
*results ProgramOutput

The ProgramOutput objects to display.

()

Returns:

Name Type Description
str str

A string of HTML displaying the ProgramOutput objects in a table.

Source code in src/qcdata/view.py
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
def generate_output_table(*results: ProgramOutput) -> str:
    """Generate an HTML table displaying the basic parameters for ProgramOutput objects.

    Args:
        *results: The ProgramOutput objects to display.

    Returns:
        str: A string of HTML displaying the ProgramOutput objects in a table.
    """
    styles = """
    <style>
        table {
            border-collapse: collapse;
            width: 100%;
        }
        th, td {
            border: 1px solid black;
            padding: 8px;
            text-align: left;
        }
        .inner-table {
            border: none;
            width: 100%;
        }
        .inner-table td {
            border: none;
            padding: 2px 5px;
        }
        .key, .value {
            text-align: left;
            white-space: nowrap;
        }
    </style>
    """

    table_header = """
    <table>
        <tr>
            <th>Structure</th>
            <th>Success</th>
            <th>Wall Time</th>
            <th>Calculation Type</th>
            <th>Requested Program</th>
            <th>Result Program</th>
            <th>Model</th>
            <th>Keywords</th>
    """
    has_files = any(res.input_data.files for res in results)
    has_subprograms = any(
        isinstance(res.input_data, ProgramInput) and res.input_data.subprograms
        for res in results
    )
    if has_files:
        table_header += "<th>Input Files</th>"

    if has_subprograms:
        table_header += "<th>Subprograms</th>"
    table_header += "</tr>"

    table_rows = []
    for res in results:
        structured = (
            res.input_data if isinstance(res.input_data, ProgramInput) else None
        )
        subject = None
        if structured is not None:
            subject = structured.structure or structured.initial_chain[0]
        success_style = (
            'style="color: green; font-weight: bold;"'
            if res.success
            else 'style="color: red; font-weight: bold;"'
        )
        base_row = f"""
        <tr>
            <td>{
            generate_dictionary_string(
                {
                    "charge": subject.charge,
                    "multiplicity": subject.multiplicity,
                    "name": subject.ids.name or "",
                }
            )
            if subject
            else ""
        }</td>
            <td {success_style}>{res.success}</td>
            <td> {
            _format_time(res.execution.wall_time)
            if res.execution.wall_time is not None
            else "No timing data"
        }</td>
            <td>{structured.calctype.name if structured else ""}</td>
            <td>{html.escape(res.input_data.program)}</td>
            <td>{
            f"{res.results.provenance.program} {res.results.provenance.program_version or ''}"
        }</td>
            <td>{
            generate_dictionary_string(
                structured.model.model_dump(exclude={"extras", "qcdata_version"})
            )
            if structured and structured.model
            else ""
        }</td>
            <td>{
            generate_dictionary_string(structured.keywords) if structured else ""
        }</td>
        """
        if has_files:
            base_row += f"<td>{generate_files_string(res.input_data.files)}</td>"

        if has_subprograms:
            base_row += f"<td>{generate_subprograms_string(structured.subprograms) if structured else ''}</td>"
        base_row += "</tr>"
        table_rows.append(base_row)

    table_footer = "</table>"
    return styles + table_header + "\n".join(table_rows) + table_footer

generate_optimization_plot

generate_optimization_plot(
    prog_output: ProgramOutput,
    figsize: tuple[float, float] = (6.4, 4.8),
    grid: bool = True,
) -> str

Generate a plot of the energy optimization by cycle for a single ProgramOutput.

Parameters:

Name Type Description Default
prog_output ProgramOutput

The ProgramOutput to generate the plot for.

required
figsize tuple[float, float]

The size of the figure in inches.

(6.4, 4.8)
grid bool

Whether to display grid lines on the plot.

True

Returns:

Name Type Description
str str

A string of HTML displaying the plot as a png image encoded in base64.

Source code in src/qcdata/view.py
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
def generate_optimization_plot(
    prog_output: ProgramOutput,
    figsize: tuple[float, float] = (6.4, 4.8),
    grid: bool = True,
) -> str:
    """Generate a plot of the energy optimization by cycle for a single ProgramOutput.

    Args:
        prog_output: The ProgramOutput to generate the plot for.
        figsize: The size of the figure in inches.
        grid: Whether to display grid lines on the plot.

    Returns:
        str: A string of HTML displaying the plot as a png image encoded in base64.
    """
    energies = prog_output.results.energies * constants.HARTREE_TO_KCAL_PER_MOL
    if not energies.size or not np.isfinite(energies).any():
        return "<p>No optimization energies available.</p>"
    baseline_energy = energies[0]
    relative_energies = energies - baseline_energy
    last_is_nan = np.isnan(relative_energies[-1])

    if last_is_nan:
        try:
            delta_E = relative_energies[-2]
        except IndexError:
            # If there is only one energy point, set delta_E to nan
            delta_E = np.nan
    else:
        delta_E = relative_energies[-1]

    fig, ax1 = plt.subplots(figsize=figsize)
    color = "tab:blue"
    ax1.set_xlabel("Optimization Cycle")
    ax1.set_ylabel("Relative Energy (kcal/mol)", color=color)
    ax1.plot(relative_energies, label="Energy", marker="o", color="green")
    if last_is_nan:
        ax1.plot(len(relative_energies) - 1, delta_E, marker="x", color="red")
    ax1.tick_params(axis="y", labelcolor=color)
    ax1.text(
        0.95,
        0.85,
        f"ΔE = {delta_E:.2f} kcal/mol",
        verticalalignment="top",
        horizontalalignment="right",
        transform=ax1.transAxes,
        color="black",
        fontsize=14,
    )
    plt.title("Energy Optimization by Cycle", pad=20)
    ax1.legend(loc="upper right")
    fig.tight_layout(rect=(0, 0, 1, 0.95))

    buf = io.BytesIO()
    plt.savefig(buf, format="png", bbox_inches="tight")
    buf.seek(0)
    image_base64 = base64.b64encode(buf.read()).decode("utf-8")
    buf.close()
    plt.close(fig)  # Close the figure to avoid duplicate plots
    return (
        f'<img src="data:image/png;base64,{image_base64}" alt="Energy Optimization by '
        f'Cycle" style="width: 100%; max-width: {DEFAULT_WIDTH}px;">'
    )

generate_data_table

generate_data_table(data: FileData) -> str

Generate an HTML table displaying the results.

Parameters:

Name Type Description Default
data FileData

The Data object to display.

required

Returns:

Name Type Description
str str

A string of HTML displaying the results in a table.

Source code in src/qcdata/view.py
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
def generate_data_table(data: FileData) -> str:
    """Generate an HTML table displaying the results.

    Args:
        data: The Data object to display.

    Returns:
        str: A string of HTML displaying the results in a table.
    """
    rows = ""
    for key, value in data.__dict__.items():
        if _not_empty(value):
            if key != "files":
                with _numpy_print_options(
                    threshold=10,
                    formatter={"float_kind": "{:0.2e}".format},
                ):
                    rows += f"<tr><td>{key}</td><td>{value}</td></tr>"

    # Add the files to the bottom table
    if _not_empty(data.files):
        rows += f"<tr><td>Files</td><td>{generate_files_string(data.files)}</td></tr>"

    return f"""
    <table style="width: 100%; border-collapse: collapse; text-align: left;">
        <thead>
            <tr>
                <th style="border: 1px solid black; padding: 8px;">Result</th>
                <th style="border: 1px solid black; padding: 8px;">Value</th>
            </tr>
        </thead>
        <tbody>
            {rows}
        </tbody>
    </table>
    """