Skip to content

graphics

Contains definitions associated with plotting/drawing card data.

distribution_plot(cards, bin_size=1, by='types', only=[], show_curve=True, title=None, value='power')

Produces a layered distribution plot of the specified list of cards, grouping with respect to a particular card field.

Warning

Card values which are not present (None) or variable ('*') are not displayed.

Parameters:

Name Type Description Default
cards CardList

The list of cards to plot.

required
bin_size Optional[int]

An optional bin size (width of a single histogram column).

1
by str

The Card field to group by.

'types'
only list[str]

An optional list of "groups" to restrict the results to.

[]
show_curve bool

Whether to display the normal curve in the resulting plot.

True
title Optional[str]

An optional title string for the plot.

None
value str

The Card field to use as the value to compare between groups.

'power'

Returns:

Type Description
Any

A Plotly distribution plot of the data.

Example

The following would create a plot to compare the distribution of power for all of the "hero classes".

from fab import CardList
from fab import graphics as g
from fab import meta

cards = CardList.load('data/cards.json', set_catalog=True)

g.distribution_plot(cards, by='types', only=meta.CLASS_TYPES, value='power')
Source code in fab/graphics.py
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
def distribution_plot(
    cards: CardList,
    bin_size: Optional[int] = 1,
    by: str = 'types',
    only: list[str] = [],
    show_curve: bool = True,
    title: Optional[str] = None,
    value: str = 'power'
) -> Any:
    '''
    Produces a layered distribution plot of the specified list of cards,
    grouping with respect to a particular card field.

    Tip: Warning
      Card values which are not present (`None`) or variable (`'*'`) are not
      displayed.

    Args:
      cards: The list of cards to plot.
      bin_size: An optional bin size (width of a single histogram column).
      by: The `Card` field to group by.
      only: An optional list of "groups" to restrict the results to.
      show_curve: Whether to display the normal curve in the resulting plot.
      title: An optional title string for the plot.
      value: The `Card` field to use as the value to compare between groups.

    Returns:
      A Plotly distribution plot of the data.

    Example:
      The following would create a plot to compare the distribution of power
      for all of the "hero classes".

      ```python
      from fab import CardList
      from fab import graphics as g
      from fab import meta

      cards = CardList.load('data/cards.json', set_catalog=True)

      g.distribution_plot(cards, by='types', only=meta.CLASS_TYPES, value='power')
      ```
    '''
    if value == 'cost':
        subcards = CardList([card for card in cards if isinstance(card.cost, int)])
    elif value == 'defense':
        subcards = CardList([card for card in cards if isinstance(card.defense, int)])
    elif value == 'health':
        subcards = CardList([card for card in cards if isinstance(card.health, int)])
    elif value == 'intelligence':
        subcards = CardList([card for card in cards if isinstance(card.intelligence, int)])
    elif value == 'pitch':
        subcards = CardList([card for card in cards if isinstance(card.pitch, int)])
    elif value == 'power':
        subcards = CardList([card for card in cards if isinstance(card.power, int)])
    else:
        raise Exception(f'unknown value {value}')
    if by == 'grants':
        layers = [l for l in subcards.grants() if not only or l in only]
        if value == 'cost':
            values = [[card.cost for card in subcards.filter(grants=layer) if isinstance(card.cost, int)] for layer in layers]
        elif value == 'defense':
            values = [[card.defense for card in subcards.filter(grants=layer) if isinstance(card.defense, int)] for layer in layers]
        elif value == 'health':
            values = [[card.health for card in subcards.filter(grants=layer) if isinstance(card.health, int)] for layer in layers]
        elif value == 'intelligence':
            values = [[card.intelligence for card in subcards.filter(grants=layer) if isinstance(card.intelligence, int)] for layer in layers]
        elif value == 'pitch':
            values = [[card.pitch for card in subcards.filter(grants=layer) if isinstance(card.pitch, int)] for layer in layers]
        elif value == 'power':
            values = [[card.power for card in subcards.filter(grants=layer) if isinstance(card.power, int)] for layer in layers]
        else:
            raise Exception(f'unknown value {value}')
    elif by in ['keyword', 'keywords']:
        layers = [l for l in subcards.keywords() if not only or l in only]
        if value == 'cost':
            values = [[card.cost for card in subcards.filter(keywords=layer) if isinstance(card.cost, int)] for layer in layers]
        elif value == 'defense':
            values = [[card.defense for card in subcards.filter(keywords=layer) if isinstance(card.defense, int)] for layer in layers]
        elif value == 'health':
            values = [[card.health for card in subcards.filter(keywords=layer) if isinstance(card.health, int)] for layer in layers]
        elif value == 'intelligence':
            values = [[card.intelligence for card in subcards.filter(keywords=layer) if isinstance(card.intelligence, int)] for layer in layers]
        elif value == 'pitch':
            values = [[card.pitch for card in subcards.filter(keywords=layer) if isinstance(card.pitch, int)] for layer in layers]
        elif value == 'power':
            values = [[card.power for card in subcards.filter(keywords=layer) if isinstance(card.power, int)] for layer in layers]
        else:
            raise Exception(f'unknown value {value}')
    elif by in ['rarity', 'rarities']:
        layers = [l for l in subcards.rarities() if not only or l in only]
        if value == 'cost':
            values = [[card.cost for card in subcards.filter(rarities=layer) if isinstance(card.cost, int)] for layer in layers]
        elif value == 'defense':
            values = [[card.defense for card in subcards.filter(rarities=layer) if isinstance(card.defense, int)] for layer in layers]
        elif value == 'health':
            values = [[card.health for card in subcards.filter(rarities=layer) if isinstance(card.health, int)] for layer in layers]
        elif value == 'intelligence':
            values = [[card.intelligence for card in subcards.filter(rarities=layer) if isinstance(card.intelligence, int)] for layer in layers]
        elif value == 'pitch':
            values = [[card.pitch for card in subcards.filter(rarities=layer) if isinstance(card.pitch, int)] for layer in layers]
        elif value == 'power':
            values = [[card.power for card in subcards.filter(rarities=layer) if isinstance(card.power, int)] for layer in layers]
        else:
            raise Exception(f'unknown value {value}')
    elif by in ['type', 'types']:
        layers = [l for l in subcards.types() if not only or l in only]
        if value == 'cost':
            values = [[card.cost for card in subcards.filter(types=layer) if isinstance(card.cost, int)] for layer in layers]
        elif value == 'defense':
            values = [[card.defense for card in subcards.filter(types=layer) if isinstance(card.defense, int)] for layer in layers]
        elif value == 'health':
            values = [[card.health for card in subcards.filter(types=layer) if isinstance(card.health, int)] for layer in layers]
        elif value == 'intelligence':
            values = [[card.intelligence for card in subcards.filter(types=layer) if isinstance(card.intelligence, int)] for layer in layers]
        elif value == 'pitch':
            values = [[card.pitch for card in subcards.filter(types=layer) if isinstance(card.pitch, int)] for layer in layers]
        elif value == 'power':
            values = [[card.power for card in subcards.filter(types=layer) if isinstance(card.power, int)] for layer in layers]
        else:
            raise Exception(f'unknown value {value}')
    else:
        raise Exception(f'unknown categorizer {by}')
    fig = ff.create_distplot(
        values,
        layers,
        bin_size = bin_size,
        curve_type = 'normal',
        show_curve = show_curve
    )
    fig.update_layout(
        title = title,
        xaxis_title = VALUE_TITLE[value],
        yaxis_title = 'Probability Density'
    )
    return fig

pie_chart(cards, by='types', only=[], statistic='count', title=None)

Produces a pie chart comparing the specified statistic between members of a certain grouping.

Note

In general, only int-typed statistics produce meaningful representations in this kind of plot.

Parameters:

Name Type Description Default
cards CardList

The list of cards to plot.

required
by str

The Card field to group by, being grants, keywords, rarities, or types.

'types'
only list[str]

Restricts the values specified in by to only displaying those specified.

[]
statistic str

The Card statistic to consider the "value" of each slice. May be any statistic produced by CardList.statistics().

'count'
title Optional[str]

An optional title for the chart.

None

Returns:

Type Description
Any

A plotly pie chart figure.

Source code in fab/graphics.py
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
def pie_chart(
    cards: CardList,
    by: str = 'types',
    only: list[str] = [],
    statistic: str = 'count',
    title: Optional[str] = None
) -> Any:
    '''
    Produces a pie chart comparing the specified statistic between members of a
    certain grouping.

    Note:
      In general, only `int`-typed statistics produce meaningful representations
      in this kind of plot.

    Args:
      cards: The list of cards to plot.
      by: The `Card` field to group by, being `grants`, `keywords`, `rarities`, or `types`.
      only: Restricts the values specified in `by` to only displaying those specified.
      statistic: The `Card` statistic to consider the "value" of each slice. May be any statistic produced by `CardList.statistics()`.
      title: An optional title for the chart.

    Returns:
      A plotly pie chart figure.
    '''
    groups = cards.group(by=by)
    labels = []
    values = []
    for group_name, group_cards in groups.items():
        if only and not group_name in only: continue
        labels.append(group_name)
        values.append(group_cards.statistics()[statistic])
    fig = go.Figure(data=[go.Pie(
        labels = labels,
        values = values
    )])
    fig.update_layout(title=title)
    return fig

scatter_plot(cards, x, y, by=None, only=[], title=None)

Produces a scatter plot comparing two Card fields in the specified list of cards.

Warning

Card values which are not present (None) or variable ('*') are not displayed.

Parameters:

Name Type Description Default
cards CardList

The collection of cards to plot.

required
x str

The numerical Card field to plot on the x-axis.

required
y str

The numerical Card field to plot on the y-axis.

required
by Optional[str]

An optional Card field to group by, being grants, keywords, rarities, or types.

None
only list[str]

Restricts the values specified in by to only displaying those specified.

[]
title Optional[str]

An optional title for the plot.

None

Returns:

Type Description
Any

A Plotly scatter plot figure representing the data.

Example

The following example would produce a scatterplot of card attack power over defense, with each card type color-coded.

from fab import CardList
from fab import graphics as g

cards = CardList.load('data/cards.json', set_catalog=True)

fig = g.scatter_plot(cards, x='defense', y='power', by='types')
fig.show()
Source code in fab/graphics.py
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
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
def scatter_plot(
    cards: CardList,
    x: str,
    y: str,
    by: Optional[str] = None,
    only: list[str] = [],
    title: Optional[str] = None
) -> Any:
    '''
    Produces a scatter plot comparing two `Card` fields in the specified list of
    cards.

    Tip: Warning
      Card values which are not present (`None`) or variable (`'*'`) are not
      displayed.

    Args:
      cards: The collection of cards to plot.
      x: The numerical `Card` field to plot on the x-axis.
      y: The numerical `Card` field to plot on the y-axis.
      by: An optional `Card` field to group by, being `grants`, `keywords`, `rarities`, or `types`.
      only: Restricts the values specified in `by` to only displaying those specified.
      title: An optional title for the plot.

    Returns:
      A Plotly scatter plot figure representing the data.

    Example:
      The following example would produce a scatterplot of card attack power
      over defense, with each card type color-coded.

      ```python
      from fab import CardList
      from fab import graphics as g

      cards = CardList.load('data/cards.json', set_catalog=True)

      fig = g.scatter_plot(cards, x='defense', y='power', by='types')
      fig.show()
      ```
    '''
    fig = go.Figure()
    num_traces = 0
    grouped_data = cards.group(by=by) if not by is None else {'all': cards}
    for category, data in grouped_data.items():
        if only and not category in only: continue
        xydata: list[tuple[int, int]] = list(set(
            (card[x], card[y]) for card in data if isinstance(card[x], int) and isinstance(card[y], int)
        ))
        hovertexts: list[str] = []
        for (xd, yd) in xydata:
            hovertexts.append(__compute_hovertext(
                data.filter(**{x: xd, y: yd})
            ))
        fig.add_trace(go.Scatter(
            hovertext = hovertexts,
            mode = 'markers',
            name = category,
            x = [d[0] for d in xydata],
            y = [d[1] for d in xydata]
        ))
        num_traces += 1
    fig.update_layout(
        showlegend  = num_traces > 1,
        title       = title,
        xaxis_title = VALUE_TITLE[x],
        yaxis_title = VALUE_TITLE[y]
    )
    return fig

statistic_plot(cards, card_set_catalog=None, statistic='median_power', style='lines', title=None)

Produces a graphical plot of the specified statistic over time.

The specified statistic may be any key returned by the `CardList.statistics()`
method.

Note:

o This plot requires a card set catalog to properly initialize cards.

Args:
  cards: The list of cards to plot.
  card_set_catalog: An optional catalog to use instead of `card_set.CARD_SET_CATALOG`.
  statistic: The `Card` statistic to plot over time.
  style: The general style of the resulting plot, being `lines`, `markers`, or `lines+markers`.
  title: An optional title for the plot.

Returns:
  A Plotly figure of the data.

Example:
  The following would produce a scatterplot of the total defense of cards
  over time:

  ```python
  from fab import CardList, CardSetCatalog
  from fab import graphics as g

  cards = CardList.load('data/cards.json', set_catalog=True)
  card_Sets = CardSetCatalog.load('data/card-sets.json', set_catalog=True)

  fig = g.statistic_plot(cards, statistic='total_defense', style='markers')
  fig.show()
  ```
Source code in fab/graphics.py
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
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
def statistic_plot(
    cards: CardList,
    card_set_catalog: Optional[CardSetCollection] = None,
    statistic: str = 'median_power',
    style: str = 'lines',
    title: Optional[str] = None
) -> Any:
    '''
    Produces a graphical plot of the specified statistic over time.

    The specified statistic may be any key returned by the `CardList.statistics()`
    method.

    Note:
o      This plot requires a card set catalog to properly initialize cards.

    Args:
      cards: The list of cards to plot.
      card_set_catalog: An optional catalog to use instead of `card_set.CARD_SET_CATALOG`.
      statistic: The `Card` statistic to plot over time.
      style: The general style of the resulting plot, being `lines`, `markers`, or `lines+markers`.
      title: An optional title for the plot.

    Returns:
      A Plotly figure of the data.

    Example:
      The following would produce a scatterplot of the total defense of cards
      over time:

      ```python
      from fab import CardList, CardSetCatalog
      from fab import graphics as g

      cards = CardList.load('data/cards.json', set_catalog=True)
      card_Sets = CardSetCatalog.load('data/card-sets.json', set_catalog=True)

      fig = g.statistic_plot(cards, statistic='total_defense', style='markers')
      fig.show()
      ```
    '''
    card_sets = card_set_catalog if not card_set_catalog is None else card_set.CARD_SET_CATALOG
    if card_sets is None:
        raise Exception('specified card set catalog has not been initialized')
    fig = go.Figure()
    release_dates = sorted(card_sets.release_dates())
    stats = []
    for date in release_dates:
        stats.append(
            CardList([card for card in cards if card_sets.get_release_date(card) == date]).statistics()[statistic]
        )
    fig.add_trace(go.Scatter(
        mode = style,
        name = f'{STAT_TITLE[statistic]}',
        x = release_dates,
        y = stats,
    ))
    fig.update_layout(
        showlegend = False,
        title = title,
        xaxis_title = 'Date',
        yaxis_title = STAT_TITLE[statistic]
    )
    return fig

table(cards, columns=['name', 'type_text', 'cost', 'defense', 'pitch', 'power', 'grants', 'keywords'])

Renders a table for the specified collection of cards.

Parameters:

Name Type Description Default
cards CardList

The collection of cards to render.

required
columns list[str]

Specifies the list of columns to render, corresponding to Card object fields.

['name', 'type_text', 'cost', 'defense', 'pitch', 'power', 'grants', 'keywords']

Returns:

Type Description
Any

A Plotly figure representing the table data.

Source code in fab/graphics.py
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
def table(cards: CardList, columns: list[str] = ['name', 'type_text', 'cost', 'defense', 'pitch', 'power', 'grants', 'keywords']) -> Any:
    '''
    Renders a table for the specified collection of cards.

    Args:
      cards: The collection of cards to render.
      columns: Specifies the list of columns to render, corresponding to `Card` object fields.

    Returns:
      A Plotly figure representing the table data.
    '''
    fig = go.Figure()
    cells = []
    for column in columns:
        c = [card[column] for card in cards]
        cells.append(c)
    fig.add_trace(go.Table(
        header = {
            'align': 'left',
            'values': [VALUE_TITLE[column] for column in columns],
        },
        cells = {
            'align': 'left',
            'values': cells
        }
    ))
    return fig