【问题标题】:Creating publication-quality geometric figures in Python在 Python 中创建出版质量的几何图形
【发布时间】:2020-10-08 22:45:53
【问题描述】:

我是一名数学家。最近,我成为一家知名杂志的困惑与问题专栏的编辑。有时,我需要创建一个图形来伴随问题或解决方案。这些数字主要与 2D(有时是 3D)欧几里得几何(线、多边形、圆,以及偶尔的椭圆或其他圆锥截面)有关。目标是获得带有 Computer Modern ("TeX") 文本标签的高质量(可印刷)图形。我的希望是找到(或者可能帮助编写!)一个相对高级的 Python 库,它“知道”欧几里得几何,因为自然操作(例如,画一条垂直线到通过给定点的给定线,二等分给定角度,或在线 L 上反射图形 A 以获得新图形 A') 已在库中定义。当然,在定义元素后创建图形的能力是一个关键目标(例如,作为 Encapsulated Postscript)。

我知道这个问题的多个次优解决方案(一些部分),但我不知道任何既简单又灵活的解决方案。让我解释一下:

  • Asymptote(类似于/基于Metapost)允许创建极其复杂的高质量图形,但对几何构造几乎一无所知(它是一种相当低级的语言),因此任何非平凡的构造都需要相当长的时间脚本。
  • TikZ 和包 tkz-euclide 是高级的、灵活的并且还可以生成高质量的图形,但是它的语法太重了,相比之下我只是为 Python 的简单性而哭泣。 (有些程序实际上会导出到 TikZ——见下文。)
  • 我最熟悉的动态几何程序Geogebra,通常具有图形导出功能(EPS、TikZ 等),但旨在以交互方式使用。有时,人们需要的是一个基于硬规范(例如,精确的边长)的图形——在脚本中定义对象最终会更加灵活(如果相应地不太方便)。
  • 两个程序,EukleidesGCLC,最接近我正在寻找的内容:它们生成数字(EPS 格式;GCLC 也导出到 TikZ)。 Eukleides 具有所有选项中最漂亮、最简单的语法(请参阅examples),但它恰好是用 C 编写的(有可用的源代码,尽管我不确定许可证),相当有限/不可定制,并且不再维护。 GCLC 仍在维护,但它是闭源的,它的语法明显比 Eukleides 的差,并且还有一些其他不自然的怪癖。此外,它不适用于 Mac OS(我的笔记本电脑是 Mac)。

Python 有:

  • Matplotlib,它可以生成极高质量的图形(尤其是函数或数值数据),但似乎不了解几何构造,并且
  • Sympy 有一个几何模块,确实了解几何对象和构造,所有这些都可以通过令人愉快的 Python 语法访问,但似乎没有图形导出(甚至显示?)功能。

最后,一个问题:是否有一个库,例如“用于 Sympy/几何图形的图形”,它使用 Python 语法来描述几何对象和结构,从而允许生成高质量的图形(主要用于打印,例如 EPS)?

如果不存在具有此类功能的库,我会考虑帮助编写一个(也许是 Sympy 的扩展?)。我会感谢指点。

【问题讨论】:

  • 这是一个非常有趣和明确的问题,但不幸的是,图书馆推荐的请求在 SO 上显然是题外话。 :-/
  • SymPy 似乎为根据documentation here 绘制图形提供了一些支持。它使用matplotlib 作为它的后端,所以你可以利用它。
  • 有很多使用matplotlib 绘制shapely 几何图形的示例,因此如果您通过指定点和顶点来构造几何图形,shapely 可能比SymPy 更容易。如果您使用符号表达式生成几何图形,您可以考虑从 SymPy 表达式构建 shapely 几何图形,然后使用 matplotlib 绘制。
  • 也许你可以试试 Manim,即数学动画引擎。您可能没有听说过它,但您肯定听说过 Grant Sanderson 或 3Blue1Brown。这是他使用(和设计)的引擎来创建令人惊叹的数学插图。

标签: python geometry


【解决方案1】:

有一种方法可以使用matplotlob 生成矢量图像,使用库io 输出到带有this approach 的矢量图像(SVG)。

我个人尝试在该网页中以 python 文件的形式运行该方法的代码(生成矢量直方图),并且成功了。

代码:

    import numpy as np
    import matplotlib.pyplot as plt
    import xml.etree.ElementTree as ET
    from io import BytesIO
    import json
    plt.rcParams['svg.fonttype'] = 'none'
    # Apparently, this `register_namespace` method is necessary to avoid garbling
    # the XML namespace with ns0.
    ET.register_namespace("", "http://www.w3.org/2000/svg")
    # Fixing random state for reproducibility
    np.random.seed(19680801)
    # --- Create histogram, legend and title ---
    plt.figure()
    r = np.random.randn(100)
    r1 = r + 1
    labels = ['Rabbits', 'Frogs']
    H = plt.hist([r, r1], label=labels)
    containers = H[-1]
    leg = plt.legend(frameon=False)
    plt.title("From a web browser, click on the legend\n"
            "marker to toggle the corresponding histogram.")
    # --- Add ids to the svg objects we'll modify
    hist_patches = {}
    for ic, c in enumerate(containers):
        hist_patches['hist_%d' % ic] = []
        for il, element in enumerate(c):
            element.set_gid('hist_%d_patch_%d' % (ic, il))
            hist_patches['hist_%d' % ic].append('hist_%d_patch_%d' % (ic, il))
    # Set ids for the legend patches
    for i, t in enumerate(leg.get_patches()):
        t.set_gid('leg_patch_%d' % i)
    # Set ids for the text patches
    for i, t in enumerate(leg.get_texts()):
        t.set_gid('leg_text_%d' % i)
    # Save SVG in a fake file object.
    f = BytesIO()
    plt.savefig(f, format="svg")
    # Create XML tree from the SVG file.
    tree, xmlid = ET.XMLID(f.getvalue())
    # --- Add interactivity ---
    # Add attributes to the patch objects.
    for i, t in enumerate(leg.get_patches()):
        el = xmlid['leg_patch_%d' % i]
        el.set('cursor', 'pointer')
        el.set('onclick', "toggle_hist(this)")
    # Add attributes to the text objects.
    for i, t in enumerate(leg.get_texts()):
        el = xmlid['leg_text_%d' % i]
        el.set('cursor', 'pointer')
        el.set('onclick', "toggle_hist(this)")
    # Create script defining the function `toggle_hist`.
    # We create a global variable `container` that stores the patches id
    # belonging to each histogram. Then a function "toggle_element" sets the
    # visibility attribute of all patches of each histogram and the opacity
    # of the marker itself.
    script = """
    <script type="text/ecmascript">
    <![CDATA[
    var container = %s
    function toggle(oid, attribute, values) {
        /* Toggle the style attribute of an object between two values.
        Parameters
        ----------
        oid : str
        Object identifier.
        attribute : str
        Name of style attribute.
        values : [on state, off state]
        The two values that are switched between.
        */
        var obj = document.getElementById(oid);
        var a = obj.style[attribute];
        a = (a == values[0] || a == "") ? values[1] : values[0];
        obj.style[attribute] = a;
        }
    function toggle_hist(obj) {
        var num = obj.id.slice(-1);
        toggle('leg_patch_' + num, 'opacity', [1, 0.3]);
        toggle('leg_text_' + num, 'opacity', [1, 0.5]);

        var names = container['hist_'+num]

        for (var i=0; i < names.length; i++) {
            toggle(names[i], 'opacity', [1, 0])
        };
        }
    ]]>
    </script>
    """ % json.dumps(hist_patches)
    # Add a transition effect
    css = tree.getchildren()[0][0]
    css.text = css.text + "g {-webkit-transition:opacity 0.4s ease-out;" + \
        "-moz-transition:opacity 0.4s ease-out;}"
    # Insert the script and save to file.
    tree.insert(0, ET.XML(script))
    ET.ElementTree(tree).write("svg_histogram.svg")

以前,您需要在顶行 pip install 所需的库,并且它成功保存了一个带有绘图的 SVG 文件(您可以在直方图中读取文件和 zoomwant 并且不会得到任何像素,因为图像是用数学函数生成)。

它(显然在我们这个时代)使用 python 3。

然后您可以在您的 TeX 文档中导入 SVG 图像以进行发布渲染。

希望对你有帮助。

您好, 哈维尔。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-17
    • 2018-08-16
    • 1970-01-01
    • 2013-04-05
    • 1970-01-01
    • 2013-04-20
    • 2012-05-21
    相关资源
    最近更新 更多