|
81 | 81 |
|
82 | 82 | Add horizontal line(s) to a plot(ter). |
83 | 83 |
|
| 84 | +* :class:`aspecd.annotation.Text` |
| 85 | +
|
| 86 | + Add text(s) to a plot(ter). |
| 87 | +
|
84 | 88 |
|
85 | 89 | Module documentation |
86 | 90 | ==================== |
87 | 91 |
|
88 | 92 | """ |
89 | 93 |
|
| 94 | +import numpy as np |
| 95 | + |
90 | 96 | import aspecd.exceptions |
91 | 97 | import aspecd.history |
92 | 98 | import aspecd.plotting |
@@ -832,3 +838,221 @@ def _perform_task(self): |
832 | 838 | else: |
833 | 839 | line = self.plotter.ax.axhline(y=position) |
834 | 840 | self.drawings.append(line) |
| 841 | + |
| 842 | + |
| 843 | +class Text(PlotAnnotation): |
| 844 | + """ |
| 845 | + Text added to a plot. |
| 846 | +
|
| 847 | + One of the most versatile ways to annotate a plot is adding text labels |
| 848 | + at defined positions. Basically, this class is the ASpecD wrapper to |
| 849 | + :meth:`matplotlib.axes.Axes.text`. Basically, you provide coordinates |
| 850 | + (*x*, *y*) for the location and a text label. By default, coordinates |
| 851 | + are data coordinates and specify the bottom left corner of the text. |
| 852 | +
|
| 853 | + The properties of the texts can be controlled in quite some detail using |
| 854 | + the :attr:`properties` property. Note that all texts will share the same |
| 855 | + properties. If you need to add texts with different properties to the |
| 856 | + same plot, use several :class:`Text` objects and annotate separately. |
| 857 | +
|
| 858 | +
|
| 859 | + Attributes |
| 860 | + ---------- |
| 861 | + parameters : :class:`dict` |
| 862 | + All parameters necessary for the annotation, implicit and explicit |
| 863 | +
|
| 864 | + The following keys exist: |
| 865 | +
|
| 866 | + positions : :class:`list` |
| 867 | + List of the positions texts should appear at. |
| 868 | +
|
| 869 | + Note that each position is itself a list: [*x*, *y*] |
| 870 | +
|
| 871 | + Values are in axis (data) units. |
| 872 | +
|
| 873 | + xpositions : :class:`list` |
| 874 | + List of the *x* positions texts should appear at. |
| 875 | +
|
| 876 | + This allows to set *x* positions from the result of other tasks, |
| 877 | + *e.g.* a peak finding analysis step. |
| 878 | +
|
| 879 | + If ``xpositions`` is set, you need to set ``ypositions`` as well. |
| 880 | + However, you can set either a single element or even a scalar |
| 881 | + (not a list). In this case, the single *y* position is expanded |
| 882 | + to match the number of *x* positions, *i.e.*, all texts will |
| 883 | + appear with the same *y* position. |
| 884 | +
|
| 885 | + If you provide both, ``positions`` and |
| 886 | + ``xpositions``/``ypositions``, the latter couple wins. |
| 887 | +
|
| 888 | + Values are in axis (data) units. |
| 889 | +
|
| 890 | + ypositions : :class:`list` or :class:`float` |
| 891 | + List of the *y* positions texts should appear at. |
| 892 | +
|
| 893 | + If ``xpositions`` is set, you need to set ``ypositions`` as well. |
| 894 | + However, you can set either a single element or even a scalar |
| 895 | + (not a list). In this case, the single *y* position is expanded |
| 896 | + to match the number of *x* positions, *i.e.*, all texts will |
| 897 | + appear with the same *y* position. |
| 898 | +
|
| 899 | + If you provide both, ``positions`` and |
| 900 | + ``xpositions``/``ypositions``, the latter couple wins. |
| 901 | +
|
| 902 | + Values are in axis (data) units. |
| 903 | +
|
| 904 | + texts : :class:`list` |
| 905 | + Texts that should appear at the individual positions. |
| 906 | +
|
| 907 | + Each text is a :class:`str`, obviously. |
| 908 | +
|
| 909 | + properties : :class:`aspecd.plotting.TextProperties` |
| 910 | + Properties of the text(s) within a plot |
| 911 | +
|
| 912 | + For the properties that can be set this way, see the documentation |
| 913 | + of the :class:`aspecd.plotting.TextProperties` class. |
| 914 | +
|
| 915 | + Examples |
| 916 | + -------- |
| 917 | + For convenience, a series of examples in recipe style (for details of |
| 918 | + the recipe-driven data analysis, see :mod:`aspecd.tasks`) is given below |
| 919 | + for how to make use of this class. The examples focus each on a single |
| 920 | + aspect. |
| 921 | +
|
| 922 | + Generally and for obvious reasons, you need to have both, a plot task |
| 923 | + and a plotannotation task. It does not really matter which task you |
| 924 | + define first, the plot or the plot annotation. There are only marginal |
| 925 | + differences, and both ways are shown below. |
| 926 | +
|
| 927 | + .. code-block:: yaml |
| 928 | +
|
| 929 | + - kind: singleplot |
| 930 | + type: SinglePlotter1D |
| 931 | + properties: |
| 932 | + filename: plot1D.pdf |
| 933 | + result: plot1D |
| 934 | +
|
| 935 | + - kind: plotannotation |
| 936 | + type: Text |
| 937 | + properties: |
| 938 | + parameters: |
| 939 | + positions: |
| 940 | + - [0.5, 0.5] |
| 941 | + - [1.0, 0.5] |
| 942 | + texts: |
| 943 | + - "Lorem ipsum" |
| 944 | + - "dolor sit amet" |
| 945 | + properties: |
| 946 | + color: green |
| 947 | + fontsize: large |
| 948 | + fontstyle: oblique |
| 949 | + rotation: 30 |
| 950 | + plotter: plot1D |
| 951 | +
|
| 952 | +
|
| 953 | + In this case, the plotter is defined first, and the annotation second. |
| 954 | + To refer to the plotter from within the plotannotation task, you need to |
| 955 | + set the ``result`` attribute in the plotting task and refer to it within |
| 956 | + the ``plotter`` attribute of the plotannotation task. Although defining |
| 957 | + the plotter before the annotation, the user still expects the annotation |
| 958 | + to be included in the file containing the actual plot, despite the fact |
| 959 | + that the figure has been saved (for the first time) before the |
| 960 | + annotation has been added. |
| 961 | +
|
| 962 | + Sometimes, it might be convenient to go the other way round and first |
| 963 | + define an annotation and afterwards add it to a plot(ter). This can be |
| 964 | + done as well: |
| 965 | +
|
| 966 | + .. code-block:: yaml |
| 967 | +
|
| 968 | + - kind: plotannotation |
| 969 | + type: Text |
| 970 | + properties: |
| 971 | + parameters: |
| 972 | + positions: |
| 973 | + - [0.5, 0.5] |
| 974 | + - [1.0, 0.5] |
| 975 | + texts: |
| 976 | + - "Lorem ipsum" |
| 977 | + - "dolor sit amet" |
| 978 | + properties: |
| 979 | + color: green |
| 980 | + fontsize: large |
| 981 | + fontstyle: oblique |
| 982 | + rotation: 30 |
| 983 | + result: text |
| 984 | +
|
| 985 | + - kind: singleplot |
| 986 | + type: SinglePlotter1D |
| 987 | + properties: |
| 988 | + filename: plot1D.pdf |
| 989 | + annotations: |
| 990 | + - text |
| 991 | +
|
| 992 | +
|
| 993 | + In this way, you can add the same annotation to several plots, |
| 994 | + and be sure that each annotation is handled as a separate object. |
| 995 | +
|
| 996 | + Suppose you have more than one plotter you want to apply an annotation |
| 997 | + to. In this case, the ``plotter`` property of the plotannotation task is |
| 998 | + a list rather than a string: |
| 999 | +
|
| 1000 | + .. code-block:: yaml |
| 1001 | +
|
| 1002 | + - kind: singleplot |
| 1003 | + type: SinglePlotter1D |
| 1004 | + result: plot1 |
| 1005 | +
|
| 1006 | + - kind: singleplot |
| 1007 | + type: SinglePlotter1D |
| 1008 | + result: plot2 |
| 1009 | +
|
| 1010 | + - kind: plotannotation |
| 1011 | + type: Text |
| 1012 | + properties: |
| 1013 | + parameters: |
| 1014 | + positions: |
| 1015 | + - [0.5, 0.5] |
| 1016 | + - [1.0, 0.5] |
| 1017 | + texts: |
| 1018 | + - "Lorem ipsum" |
| 1019 | + - "dolor sit amet" |
| 1020 | + plotter: |
| 1021 | + - plot1 |
| 1022 | + - plot2 |
| 1023 | +
|
| 1024 | + In this case, the annotation will be applied to both plots |
| 1025 | + independently. Note that the example has been reduced to the key |
| 1026 | + aspects. In a real situation, the two plotters will differ much more. |
| 1027 | +
|
| 1028 | +
|
| 1029 | + .. versionadded:: 0.10 |
| 1030 | +
|
| 1031 | + """ |
| 1032 | + |
| 1033 | + def __init__(self): |
| 1034 | + super().__init__() |
| 1035 | + self.parameters["positions"] = [] |
| 1036 | + self.parameters["xpositions"] = [] |
| 1037 | + self.parameters["ypositions"] = [] |
| 1038 | + self.parameters["texts"] = [] |
| 1039 | + self.properties = aspecd.plotting.TextProperties() |
| 1040 | + |
| 1041 | + def _perform_task(self): |
| 1042 | + if self.parameters["xpositions"] and self.parameters["ypositions"]: |
| 1043 | + xpositions = self.parameters["xpositions"] |
| 1044 | + ypositions = self.parameters["ypositions"] |
| 1045 | + if np.isscalar(ypositions): |
| 1046 | + ypositions = [ypositions] * len(xpositions) |
| 1047 | + if len(ypositions) == 1: |
| 1048 | + ypositions = ypositions * len(xpositions) |
| 1049 | + positions = [] |
| 1050 | + for idx, xposition in enumerate(xpositions): |
| 1051 | + positions.append([xposition, ypositions[idx]]) |
| 1052 | + else: |
| 1053 | + positions = self.parameters["positions"] |
| 1054 | + for idx, position in enumerate(positions): |
| 1055 | + text = self.plotter.ax.text( |
| 1056 | + position[0], position[1], self.parameters["texts"][idx] |
| 1057 | + ) |
| 1058 | + self.drawings.append(text) |
0 commit comments