aboutsummaryrefslogtreecommitdiff
path: root/venv/lib/python3.8/site-packages/plotly/figure_factory/utils.py
blob: e20a319af13cc31e14063bac021eb2103d24d34b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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
from collections.abc import Sequence

from plotly import exceptions


def is_sequence(obj):
    return isinstance(obj, Sequence) and not isinstance(obj, str)


def validate_index(index_vals):
    """
    Validates if a list contains all numbers or all strings

    :raises: (PlotlyError) If there are any two items in the list whose
        types differ
    """
    from numbers import Number

    if isinstance(index_vals[0], Number):
        if not all(isinstance(item, Number) for item in index_vals):
            raise exceptions.PlotlyError(
                "Error in indexing column. "
                "Make sure all entries of each "
                "column are all numbers or "
                "all strings."
            )

    elif isinstance(index_vals[0], str):
        if not all(isinstance(item, str) for item in index_vals):
            raise exceptions.PlotlyError(
                "Error in indexing column. "
                "Make sure all entries of each "
                "column are all numbers or "
                "all strings."
            )


def validate_dataframe(array):
    """
    Validates all strings or numbers in each dataframe column

    :raises: (PlotlyError) If there are any two items in any list whose
        types differ
    """
    from numbers import Number

    for vector in array:
        if isinstance(vector[0], Number):
            if not all(isinstance(item, Number) for item in vector):
                raise exceptions.PlotlyError(
                    "Error in dataframe. "
                    "Make sure all entries of "
                    "each column are either "
                    "numbers or strings."
                )
        elif isinstance(vector[0], str):
            if not all(isinstance(item, str) for item in vector):
                raise exceptions.PlotlyError(
                    "Error in dataframe. "
                    "Make sure all entries of "
                    "each column are either "
                    "numbers or strings."
                )


def validate_equal_length(*args):
    """
    Validates that data lists or ndarrays are the same length.

    :raises: (PlotlyError) If any data lists are not the same length.
    """
    length = len(args[0])
    if any(len(lst) != length for lst in args):
        raise exceptions.PlotlyError(
            "Oops! Your data lists or ndarrays should be the same length."
        )


def validate_positive_scalars(**kwargs):
    """
    Validates that all values given in key/val pairs are positive.

    Accepts kwargs to improve Exception messages.

    :raises: (PlotlyError) If any value is < 0 or raises.
    """
    for key, val in kwargs.items():
        try:
            if val <= 0:
                raise ValueError("{} must be > 0, got {}".format(key, val))
        except TypeError:
            raise exceptions.PlotlyError("{} must be a number, got {}".format(key, val))


def flatten(array):
    """
    Uses list comprehension to flatten array

    :param (array): An iterable to flatten
    :raises (PlotlyError): If iterable is not nested.
    :rtype (list): The flattened list.
    """
    try:
        return [item for sublist in array for item in sublist]
    except TypeError:
        raise exceptions.PlotlyError(
            "Your data array could not be "
            "flattened! Make sure your data is "
            "entered as lists or ndarrays!"
        )


def endpts_to_intervals(endpts):
    """
    Returns a list of intervals for categorical colormaps

    Accepts a list or tuple of sequentially increasing numbers and returns
    a list representation of the mathematical intervals with these numbers
    as endpoints. For example, [1, 6] returns [[-inf, 1], [1, 6], [6, inf]]

    :raises: (PlotlyError) If input is not a list or tuple
    :raises: (PlotlyError) If the input contains a string
    :raises: (PlotlyError) If any number does not increase after the
        previous one in the sequence
    """
    length = len(endpts)
    # Check if endpts is a list or tuple
    if not (isinstance(endpts, (tuple)) or isinstance(endpts, (list))):
        raise exceptions.PlotlyError(
            "The intervals_endpts argument must "
            "be a list or tuple of a sequence "
            "of increasing numbers."
        )
    # Check if endpts contains only numbers
    for item in endpts:
        if isinstance(item, str):
            raise exceptions.PlotlyError(
                "The intervals_endpts argument "
                "must be a list or tuple of a "
                "sequence of increasing "
                "numbers."
            )
    # Check if numbers in endpts are increasing
    for k in range(length - 1):
        if endpts[k] >= endpts[k + 1]:
            raise exceptions.PlotlyError(
                "The intervals_endpts argument "
                "must be a list or tuple of a "
                "sequence of increasing "
                "numbers."
            )
    else:
        intervals = []
        # add -inf to intervals
        intervals.append([float("-inf"), endpts[0]])
        for k in range(length - 1):
            interval = []
            interval.append(endpts[k])
            interval.append(endpts[k + 1])
            intervals.append(interval)
        # add +inf to intervals
        intervals.append([endpts[length - 1], float("inf")])
        return intervals


def annotation_dict_for_label(
    text,
    lane,
    num_of_lanes,
    subplot_spacing,
    row_col="col",
    flipped=True,
    right_side=True,
    text_color="#0f0f0f",
):
    """
    Returns annotation dict for label of n labels of a 1xn or nx1 subplot.

    :param (str) text: the text for a label.
    :param (int) lane: the label number for text. From 1 to n inclusive.
    :param (int) num_of_lanes: the number 'n' of rows or columns in subplot.
    :param (float) subplot_spacing: the value for the horizontal_spacing and
        vertical_spacing params in your plotly.tools.make_subplots() call.
    :param (str) row_col: choose whether labels are placed along rows or
        columns.
    :param (bool) flipped: flips text by 90 degrees. Text is printed
        horizontally if set to True and row_col='row', or if False and
        row_col='col'.
    :param (bool) right_side: only applicable if row_col is set to 'row'.
    :param (str) text_color: color of the text.
    """
    temp = (1 - (num_of_lanes - 1) * subplot_spacing) / (num_of_lanes)
    if not flipped:
        xanchor = "center"
        yanchor = "middle"
        if row_col == "col":
            x = (lane - 1) * (temp + subplot_spacing) + 0.5 * temp
            y = 1.03
            textangle = 0
        elif row_col == "row":
            y = (lane - 1) * (temp + subplot_spacing) + 0.5 * temp
            x = 1.03
            textangle = 90
    else:
        if row_col == "col":
            xanchor = "center"
            yanchor = "bottom"
            x = (lane - 1) * (temp + subplot_spacing) + 0.5 * temp
            y = 1.0
            textangle = 270
        elif row_col == "row":
            yanchor = "middle"
            y = (lane - 1) * (temp + subplot_spacing) + 0.5 * temp
            if right_side:
                x = 1.0
                xanchor = "left"
            else:
                x = -0.01
                xanchor = "right"
            textangle = 0

    annotation_dict = dict(
        textangle=textangle,
        xanchor=xanchor,
        yanchor=yanchor,
        x=x,
        y=y,
        showarrow=False,
        xref="paper",
        yref="paper",
        text=text,
        font=dict(size=13, color=text_color),
    )
    return annotation_dict


def list_of_options(iterable, conj="and", period=True):
    """
    Returns an English listing of objects seperated by commas ','

    For example, ['foo', 'bar', 'baz'] becomes 'foo, bar and baz'
    if the conjunction 'and' is selected.
    """
    if len(iterable) < 2:
        raise exceptions.PlotlyError(
            "Your list or tuple must contain at least 2 items."
        )
    template = (len(iterable) - 2) * "{}, " + "{} " + conj + " {}" + period * "."
    return template.format(*iterable)