Гайфутдинов Ильнур \ Блог

python, matplotlib, построение диаграмм

Понадобилось мне отрисовывать вот такие графики для блога, поэтому написал скрипт на python

matplotlib barh

Настройка окружения

Создаем папку для приложения, например gii_plot_creator

Инициализируем git репозитории

gii_plot_creator> git init

Добавляем файл для гита, в котором пропишем исключения, т.е. те данные которые не должны попадать в репозитории:

gii_plot_creator/.gitignore

venv

Создаем виртуальное окружение python.

Этим мы создадим папку venv в папке проекта, где будет лежать python и библиотеки проекта

gii_plot_creator> python -m venv venv

Активируем python окружение

windows

gii_plot_creator> venv/Scripts/activate
(venv) gii_plot_creator>

linux

gii_plot_creator> source 
(venv) gii_plot_creator>

Пишем файл зависимостей

Данный файл содержит зависимости нашего приложения, т.е. список тех библиотек, от которых зависит наш проект

requirements.txt

matplotlib

Установка зависимостей

(venv) gii_plot_creator> pip install -r requirements.txt

Файл данных

Для отображения придумаем структуру данных и запишем данные в файл, чтобы в будущем менять данные только в файле данных

gii_plot_creator/data.json

[
  {
    "title": "AIDA64",
    "x_label": "MB/sec",
    "legends": [
      "Чтение из памяти",
      "Запись в память",
      "Копирование в памяти"
    ],
    "scores": [
      ["Core-i3 2100", [19530, 20094, 19267]],
      ["Core-i3 2100\nGT710", [19454, 20145, 18861]],
      ["Core-i3 2100\nGT1030", [19377, 20266, 19069]],
      ["Ryzen 5 2600\nGT710", [16364, 15975, 15822]],
      ["Ryzen 5 2600\nGT1030", [16700, 15990, 15806]]
    ]
  }
]

Разработка скрипта

Напишем файл настроек

gii_plot_creator/settings.py

"""
конфигурация и константы приложения
"""

# модуль для работы с путями
import os

# папка приложения, gii_plot_creator
BASE_PATH = os.path.dirname(__file__)

# путь к файлу с данными
DATA_PATH = os.path.join(BASE_PATH, 'data.json')

Напишем файл, в котором придумаем исключения, которые могут возникнуть в нашем приложении

gii_plot_creator/exceptions.py

"""
исключения, выбрасываемые приложением
"""

class AppException(Exception):
    """
    базовый класс для исключений приложения
    """
    message = None

    def __str__(self):
        return self.message


class DataFileDoesNotExists(AppException):
    """
    файл данных не существует
    """
    message = 'Файла данных не существует'


class DataFileStructureError(AppException):
    """
    ошибка в структуре файла данных
    """

    def __init__(self, err):
        self.message = f'Файл данных имеет не корректный формат ({err})'

Основной файл скрипта

gii_plot_creator/app.py

"""
приложения для рисования графиков бенчмарков
"""

# модуль для работы с json данными
import json
# модуль для работы с путями
import os

# импортируем модуль для построения графиков
import matplotlib.pyplot as plt

# импортируем модуль исключении приложения
import exceptions
# импортируем модуль настроек приложения
import settings


def get_data(data_path):
    """
    функция возвращает данные для построения графиков
    :param data_path: путь до файла с данными
    :type data_path: str
    :rtype: dict or list
    """
    # проверяем, существует ли вообще путь до файла конфигурации
    # т.е. есть ли например у нас файл data.json в папке с приложением
    if not os.path.exists(data_path):
        # файла не существует, тогда выбрасываем понятное исключение
        # т.е. исключение определенного типа
        raise exceptions.DataFileDoesNotExists()

    # если дошли сюда, значит файл существует
    # будем пытаться его прочитать и преобразовать данные в структуру данных python
    # т.е. json строку преобразовываем в словарь или список
    try:
        with open(data_path, encoding='utf-8') as f:
            data = json.load(f)
    except Exception as err:
        # перехватываем все исключения
        # если не смогли преобразовать, то выбрасываем понятное исключение            
        raise exceptions.DataFileStructureError(err)

    # файл есть,  преобразовали структуру, возвращаем данные
    return data


def create_plot(data):
    """
    возвращает объекты управления с холстом
    :param data: данные для отрисовки
    :type data: dict
    :rtype: tuple
    """

    # составляет список данных для оси Х
    x_pos_ticks = [score[0] for score in data['scores']]
    x_pos = list(range(len(x_pos_ticks)))

    # собираем список сведений по показателям для оси у
    y_poss = [
        {
            'legend': legend,
            'y_pos': [],
            'rects': []
        } for legend in (data.get('legends') or (None,))
    ]
    for score in data['scores']:
        for index, score_y in enumerate(score[1]):
            y_poss[index]['y_pos'].append(score_y)

    # получаем объекты для отображения
    fig, ax = plt.subplots()

    # далее определяем высоту для линии графика, в зависимости от количества показателей
    width = (1./len(y_poss))-(0.01*len(y_poss))
    # минимальное значение показателя
    miny = None
    #максимальное значение показателя
    maxy = None
    for index, y_pos_info in enumerate(y_poss):
        legend = y_pos_info['legend']
        y_pos = y_pos_info['y_pos']

        y_pos_info['rects'].extend(
            plt.barh(
                [x - (width * index) for x in x_pos],
                y_pos,
                width,
                label=legend
            )
        )

        if miny is None:
            miny = min(y_pos)
            maxy = max(y_pos)
        else:
            miny = min((miny, min(y_pos)))
            maxy = max((maxy, max(y_pos)))

    # вычисляем дельту, она нужна будет для вычисления минимального и максимального значения для оси графика
    delta = (maxy - miny) / 100
    miny -= delta
    maxy += delta*13

    # для каждой линии показателя отрисовываем числовое значение
    for y_pos_info in y_poss:
        y_pos = y_pos_info['y_pos']
        rects = y_pos_info['rects']
        for rect, value in zip(rects, y_pos):
            ax.annotate(
                value,
                xy=(rect.get_width(), rect.get_y() - (-rect.get_height()/2)),
                xytext=(5, -4),
                textcoords='offset points',
            )


    # задаем минимальное и максимальное значение для оси Х
    ax.set_xlim(miny if miny > 0 else 0, maxy)
    # задаем значения для оси у
    ax.set_yticks(x_pos)
    # задаем отображаемые величины по оси у
    ax.set_yticklabels(x_pos_ticks)
    # устанавливаем заголовок
    ax.set_title(data['title'])

    # отрисовываем легенду, если легенда есть
    if data.get('legends'):
        ax.legend()

    # отрисовываем подпись для оси х
    if 'x_label' in data:
        ax.set_xlabel(data['x_label'])


def main():
    """
    основная функция скрипта
    """

    # получаем данные для отрисовки
    draw_data = get_data(settings.DATA_PATH)

    # рисуем каждый график в отдельный файл
    for data in draw_data:
        # отрисовываем данные 
        create_plot(data)

        # центруем данные
        plt.tight_layout()
        # сохраняем в файл
        plt.savefig('{0}.png'.format(data['title']))
        # отображаем при необходимости
        plt.show()

# перехватываем все исключения приложения
try:
    main()
except Exception as err:
    print(err)

Осталось только закомитить изменения в git

gii_plot_creator> git commit -m "application created"

Комментарии