Graphs_dart/tex/example-work.tex

493 lines
22 KiB
TeX
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

\documentclass[bachelor, och, coursework]{SCWorks}
% параметр - тип обучения - одно из значений:
% spec - специальность
% bachelor - бакалавриат (по умолчанию)
% master - магистратура
% параметр - форма обучения - одно из значений:
% och - очное (по умолчанию)
% zaoch - заочное
% параметр - тип работы - одно из значений:
% referat - реферат
% coursework - курсовая работа (по умолчанию)
% diploma - дипломная работа
% pract - отчет по практике
% pract - отчет о научно-исследовательской работе
% autoref - автореферат выпускной работы
% assignment - задание на выпускную квалификационную работу
% review - отзыв руководителя
% critique - рецензия на выпускную работу
% параметр - включение шрифта
% times - включение шрифта Times New Roman (если установлен)
% по умолчанию выключен
\usepackage[T2A]{fontenc}
\usepackage[cp1251]{inputenc}
\usepackage{graphicx}
\usepackage{minted}
\usepackage{float}
\usepackage[sort,compress]{cite}
\usepackage{amsmath}
\usepackage{amssymb}
\usepackage{amsthm}
\usepackage{fancyvrb}
\usepackage{longtable}
\usepackage{array}
\usepackage[english,russian]{babel}
\usepackage[colorlinks=true]{hyperref}
%\newcommand{\eqdef}{\stackrel {\rm def}{=}}
\newtheorem{lem}{Лемма}
\begin{document}
% Кафедра (в родительном падеже)
\chair{математической кибернетики и компьютерных наук}
% Тема работы
\title{Создание приложения для отрисовки графов и алгоритмов для работы с ними}
% Курс
\course{2}
% Группа
\group{221}
% Факультет (в родительном падеже) (по умолчанию "факультета КНиИТ")
%\department{факультета КНиИТ}
% Специальность/направление код - наименование
%\napravlenie{02.03.02 "--- Фундаментальная информатика и информационные технологии}
%\napravlenie{02.03.01 "--- Математическое обеспечение и администрирование информационных систем}
\napravlenie{09.03.01 "--- Информатика и вычислительная техника}
%\napravlenie{09.03.04 "--- Программная инженерия}
%\napravlenie{10.05.01 "--- Компьютерная безопасность}
% Для студентки. Для работы студента следующая команда не нужна.
%\studenttitle{Студентки}
% Фамилия, имя, отчество в родительном падеже
\author{Морозова Андрея Денисовича}
% Заведующий кафедрой
\chtitle{к.\,ф.-м.\,н., доцент} % степень, звание
\chname{Л.\,Б.\,Тяпаев}
%Научный руководитель (для реферата преподаватель проверяющий работу)
\satitle{доцент каф. ДМиИТ} %должность, степень, звание
\saname{О.\,В.\,Мещерякова}
% Семестр (только для практики, для остальных
% типов работ не используется)
\term{2}
% Год выполнения отчета
\date{2021}
\maketitle
% Включение нумерации рисунков, формул и таблиц по разделам
% (по умолчанию - нумерация сквозная)
% (допускается оба вида нумерации)
%\secNumbering
\tableofcontents
% Раздел "Обозначения и сокращения". Может отсутствовать в работе
%\abbreviations
%\begin{description}
% \item SQL "--- англ. structured query language — «язык структурированных запросов;
% \item $\det B$ "--- определитель матрицы $B$;
% \item ИНС "--- Искусственная нейронная сеть;
% \item FANN "--- Feedforward Artifitial Neural Network
%\end{description}
% Раздел "Определения". Может отсутствовать в работе
%\definitions
% Раздел "Определения, обозначения и сокращения". Может отсутствовать в работе.
% Если присутствует, то заменяет собой разделы "Обозначения и сокращения" и "Определения"
%\defabbr
% Раздел "Введение"
\intro
Целью настоящей работы является изучение работы фреймворка для кроссплатформенной разработки ''Flutter'' и разработка приложения для создания графов и взаимодействия с ними.
Поставлены задачи:
\begin{itemize}
\item разбор алгоритмов на графах
\item разбор работы с Flutter
\item построение приложения
\end{itemize}
\section{Введение}
\subsection{Графы}
Граф --- математический объект, состоящий из двух множеств. Одно из
них --- любое конечное множество, его элементы называются \textit{вершинами}
графа. Другое множество состоит из пар вершин, эти пары называются
\textit{ребрами} графа~\cite{IITMMM_2017}.
\subsection{Основные определения}
\textbf{Ориентированный граф} определяется как пара \textit{(V, E)}, где \textit{V} --- конечное множество, а \textit{E} --- бинарное отношение на \textit{V}, т.~е. подмножество множества ${V \times V}$. Ориентированный граф для краткости называют \textbf{орграфом}. Множество $V$ называют \textbf{множеством вершин графа}, а его элемент называют \textbf{вершиной} графа. Множество $E$ называют \textbf{множеством рёбер}, а его элементы называют \textbf{рёбрами}. Граф может содержать \textbf{рёбра-циклы}, соединяющие вершину с собой. На рисунке~\ref{fig:orgrapf_example} изображен ориентированный граф с множеством вершин \{0, 1, 2, 3, 4\}.~\cite{Algo_2013}
\begin{figure}[!ht]
\centering
\includegraphics[width=9cm]{./pic/orgraph.png}
\caption{\label{fig:orgrapf_example}
Пример орграфа}
\end{figure}
В \textbf{неориентированном} графе $G = (V, E)$ множество ребер состоит из \textbf{неупорядоченных} пар вершин: парами являются множества $\{u, v\}$, где $u, v \in V$ и $u \neq v$. Для неориентированного графа $\{u, v\}$ и $\{v, u\}$ обозначают одно и то же ребро. Неориентированный граф не может содержать рёбер-циклов, и каждое ребро состоит из двух различных вершин. На рисунке~\ref{fig:grapf_example} изображен неориентированный граф с множеством вершин \{0, 1, 2, 2, 4\}
\begin{figure}[!ht]
\centering
\includegraphics[width=9cm]{./pic/graph.png}
\caption{\label{fig:grapf_example}
Пример неориентированного графа}
\end{figure}
Вершина \textit{v} \textbf{смежна} с вершиной \textit{u}, если в графе имеется ребро $\{u, v\}$. Для неориентированных графов отношение смежности является симметричным, но для ориентированных графов это не обязательно.
\textbf{Степенью} вершины в неориентированном графе называется число инцидентных ей рёбер. Для ориентированного графа различают исходящую степень, определяемую как число выходящих из неё рёбер, и входящую степень, определяемую как число входящих в неё рёбер. Сумма исходящей и входящей степеней называется степенью вершины.
\textbf{Маршрутом} в графе называется конечная чередующаяся последовательность смежных вершин и ребер, соединяющих эти вершины.
Маршрут называется открытым, если его начальная и конечная вершины различны, в противном случае он называется замкнутым.
Маршрут называется \textbf{цепью}, если все его ребра различны. Открытая цепь называется \textbf{путем}, если все ее вершины различны.
Замкнутая цепь называется \textbf{циклом}, если различны все ее вершины, за исключением концевых.
Граф называется \textbf{связным}, если для любой пары вершин существует соединяющий их путь.~\cite{intuit}
\subsection{Представление графа}
Граф можно описать несколькими способами.
\begin{enumerate}
\item Перечисление элементов:~\ref{ref:list}.
\item Матрица смежности:~\ref{ref:smej}.
\item Матрица инцидентности:~\ref{ref:incident}.
\item Список смежности:~\ref{ref:spisok}.
\end{enumerate}
Рассмотрим на примере графа на рисунке~\ref{fig:graph_prim1}.
\begin{figure}
\centering
\includegraphics[width=9cm]{./pic/prim1.png}
\caption{\label{fig:graph_prim1}
Рассматриваемый граф}
\end{figure}
\subsubsection{Перечисление элементов}\label{ref:list}
Исходя из определения, для того, чтобы задать граф, достаточно перечислить его вершины и ребра (т.е. пары вершин).
Пример:
$$ V = \{a, b, c, d, e\} $$
$$ E = \{(a, b), (a, c), (a, e), (b, c), (b, d), (c, e), (d, e)\}$$
\subsubsection{Матрица смежности}\label{ref:smej}
Пусть $G$ --- граф с $n$ вершинами, пронумерованными числами от 1 до $n$. \textbf{Матрица смежности} --- это таблица с $n$ строками и $n$ столбцами, в которой элемент, стоящий на пересечении строки с номером $i$ и столбца с номером $j$, равен 1, если вершины с номерами $i$ и $j$ смежны, и 0 в противоположном случае.
\begin{table}[!ht]
%\centering
\caption{Пример матрицы смежности}
\begin{tabular}{|l|c|c|c|c|}
\hline 0 & 1 & 1 & 0 & 1\\
\hline 1 & 0 & 1 & 1 & 0\\
\hline 1 & 1 & 0 & 0 & 1\\
\hline 0 & 1 & 0 & 0 & 1\\
\hline 1 & 0 & 1 & 1 & 0\\
\hline
\end{tabular}
\end{table}
\subsubsection{Матрица инцидентности}\label{ref:incident}
Пусть $G$ --- граф с вершинами, пронумерованными числами от 1 до $n$, и ребрами, пронумерованными от 1 до $m$. В матрице инцидентности строки соответствуют вершинам, а столбцы рёбрам. На пересечении строки с номером $i$ и столбца с номером $j$ стоит 1, если вершина с номером $i$ инцидентна ребру с номером $j$, и 0 в противном случае.
\begin{table}[!ht]
%\centering
\caption{Пример матрицы инцидентности}
\begin{tabular}{|c|c|c|c|c|c|c|}
\hline 1 & 1 & 1 & 0 & 0 & 0 & 0\\
\hline 1 & 0 & 0 & 1 & 1 & 0 & 0\\
\hline 0 & 1 & 0 & 1 & 0 & 1 & 0\\
\hline 0 & 0 & 0 & 0 & 1 & 0 & 1\\
\hline 0 & 0 & 1 & 0 & 0 & 1 & 1\\
\hline
\end{tabular}
\end{table}
\subsubsection{Списки смежности}\label{ref:spisok}
Списки смежности часто используются для компьютерного представления графов. Для каждой вершины задается список всех смежных с ней вершин. В структурах данных, применяемых в программировании, списки смежности могут быть реализованы как массив линейных списков.
Пример:
\begin{itemize}
\item[1] : 2, 3, 5
\item[2] : 1, 3, 4
\item[3] : 1, 2, 5
\item[4] : 2, 5
\item[5] : 1, 3, 4
\end{itemize}
\subsection{Алгоритмы}
\subsubsection{Обход в ширину}
Обход в ширину --- один из основных алгоритмов на графах.
Пусть задан граф $G = (V, E)$ и выделена исходная вершина $s$. Алгоритм поиска в ширину систематически обходит все ребра $G$ для «открытия» всех вершин, достижимых из $s$, вычисляя минимальное количество рёбер от $s$ s до каждой достижимой из $s$ вершины.
Поиск в ширину имеет такое название потому, что в процессе обхода мы идём вширь, то есть перед тем как приступить к поиску вершин на расстоянии $k + 1$, выполняется обход вершин на расстоянии $k$~\cite{Algo_2013}.
Приведенная ниже процедура поиска в ширину BFS предполагает, что входной граф $G = (V, E)$ представлен при помощи списков смежности. Псевдокод алгоритма:
\inputminted[fontsize=\footnotesize, linenos]{python}{./dart/pseudoBFS.txt}
Воспользуемся групповым анализом. Операции внесения в очередь и удаление из нее требует $O(1)$ времени. Следовательно на очередь потребуется $O(V)$ времени. Т.к. максимальная длина списков смежности $\theta(E)$, то время, необходимое для сканирования списков, равно $O(E)$. Расходы на инициализацию: $O(V)$. Таким образом, общее время работы алгоритма: $O(V + E)$. Время поиска в ширину линейно зависит от размера представления графа.
\subsubsection{Обход в глубину}
Стратегия обхода в ширину состоит в том, чтобы идти ''вглубь'' графа. Сначала исследуются ребра, выходящие из вершины, открытой последней, и покидаем вершину, когда не остается неисследованных ребер --- при этом происходит возврат в вершину, из которой была открыта вершина $v$. Процесс продолжается, пока не будут открыты все вершины, достижимые из исходной.
Псевдо код алгоритма:
\inputminted[fontsize=\footnotesize, linenos]{python}{./dart/pseudoDFS.txt}
\subsubsection{Алгоритм Дейкстры}
Алгоритм был предложен голландским исследователем Эдсгером Дейкстрой в 1959 году. Используется для поиска кратчайшего пути от одной вершины до всех остальных. Используется только если вес ребер неотрицательный.
В алгоритме поддерживается множество вершин $U$, для которых уже вычислены длины кратчайших путей до них из $s$. На каждой итерации основного цикла выбирается вершина, которой на текущий момент соответствует минимальная оценка кратчайшего пути. Вершина $u$ добавляется в множество $U$ и производится релаксация всех исходящих из неё рёбер.
Псевдокод алгоритма:
\inputminted[fontsize=\footnotesize, linenos]{python}{./dart/pseudoDijkstra.txt}
\subsubsection{Алгоритм Краскала}
Алгоритм Краскала — алгоритм поиска минимального остовного дерева во взвешенном неориентированном связном графе. Был описан Джозефом Краскалом в 1956 году.
Алгоритм Краскала изначально помещает каждую вершину в своё дерево, а затем постепенно объединяет эти деревья, объединяя на каждой итерации два некоторых дерева некоторым ребром. Перед началом выполнения алгоритма, все рёбра сортируются по весу (в порядке неубывания). Затем начинается процесс объединения: перебираются все рёбра от первого до последнего (в порядке сортировки), и если у текущего ребра его концы принадлежат разным поддеревьям, то эти поддеревья объединяются, а ребро добавляется к ответу. По окончании перебора всех рёбер, все вершины окажутся принадлежащими одному поддереву.\cite{krusc}
\inputminted[fontsize=\footnotesize, linenos]{python}{./dart/pseudoKruscal.txt}
\section{Инструменты}
Рассмотрим используемый язык и библиотеку для отрисовки.
\subsection{Dart}
В качестве основы используется язык \textbf{Dart}, разработанный компанией Google, и широко используемый для кросс-платформенной разработки~\cite{dart_web}.
Dart позиционируется в качестве замены/альтернативы JavaScript. Один из разработчиков языка Марк Миллер (Mark S. Miller) писал, что JavaScript «имеет фундаментальные изъяны»\cite{futureOfJavascript}, которые невозможно исправить.
Версия 1.0 вышла в 14 ноября 2013 года.
Вторая версия была выпущена в августе 2018 года. В языке появилась надежная система типов, т.~е. во время выполнении программы все переменные будут гарантированно указанному типу~\cite{dart_sound}.
В Dart используется сборщик мусора. Синтаксис похож на языки: JavaScript, C\#, Java~\cite{dartInAction}.
Пример HelloWorld на Dart:
\inputminted[fontsize=\footnotesize, linenos]{dart}{./dart/helloWorld.dart}
Dart поддерживает сокращенную запись. Код примера HelloWorld можно записать так:
\inputminted[fontsize=\footnotesize, linenos]{dart}{./dart/hW.dart}
Концепты языка~\cite{dart_tour}:
\begin{itemize}
\item Все, что можно поместить в переменную, является объектом, а каждый объект является частью класса;
\item Dart -- язык со строгой типизацией, но объявления типов опциональны, т.к. поддерживается определение типа при компиляции;
\item В версии языка 2.12 появилась Null безопасность. Каждый объект не может быть Null, если не указано обратное;
\item поддерживаются дженерики;
\item для объявления локальных функций и переменных необходимо начать имя со знака ''\_''
\end{itemize}
.
\subsection{Flutter}
Для отрисовки информации используется фреймворк c открытым исходным кодом ''Flutter'', разработанный компанией Google.
Flutter не использует нативные компоненты для отрисовки интерфейса. В его основе лежит графический движок ''Skia'', написанный преимущественно на С++.
Skia --- библиотека для работы с 2D графикой с открытым исходным кодом, поддерживающая работу на большом количестве платформ~\cite{skia_docs}
Первая версия выпущена в 2015 году под названием ''Sky'', работала только для Android-приложений. Основная заявленная особенность --- высокая графическая производительность (возможность отображения 120 кадров в секунду). Полная поддержка создания веб-приложений появилась в версии 2.0 (март 2021 года), также разрабатывается поддержка создания настольных приложений для Windows, macOS и Linux и Google Fuchsia.
Простейший пример приложения с использованием Flutter:
\inputminted[fontsize=\footnotesize, linenos]{dart}{./dart/flutter.dart}
Из кода видно, что интерфейс описывается с помощью виджетов.
Результат приложения изображен на рисунке~\ref{fig:flutter_example}.
\newpage
\begin{figure}[!ht]
\centering
\includegraphics[width=11cm]{./pic/flutter0.png}
\caption{\label{fig:flutter_example}
Простейшее приложение на Flutter}
\end{figure}
\section{Реализация}
Структура программы разбита на 4 файла:
\begin{itemize}
\item main.dart --- точка входа в программу;
\item drawing\_page.dart --- страница с описанием работы кнопок;
\item curve\_painter.dart --- функционал для отрисовки графа;
\item graph.dart --- класс для хранения графа и манипуляции с ним.
\end{itemize}
\subsection{Графы}
Информация хранится в виде списков смежности.
Полный код можно посмотреть в приложении~\ref{graph}.
\subsubsection{Класс для хранения вершины}
Класс \textbf{Dot} используется для хранения информации о ребрах, исходящих из данной вершины.
Основная информация:
\inputminted[fontsize=\footnotesize, linenos]{dart}{./dart/graph/dot.dart}
\textit{String \_name} используется для хранения имени, \textit{int num} используется для хранения номера вершины.
Информация о ребрах задается в виде \textit{Map<int, int> \_ln}, где каждой вершине можно присвоить вес ребра.
Создать вершину можно тремя способами:
\begin{enumerate}
\item Пустая точка;
\item из двух списков, где в первом список вершин, а во втором - длины пути;
\item из \textit{Map<int, int>}.
\end{enumerate}
\inputminted[fontsize=\footnotesize, linenos]{dart}{./dart/graph/dot_constr.dart}
Для манипуляций с информацией доступно:
\begin{enumerate}
\item изменение имени;
\item добавление пути;
\item удаление пути.
\end{enumerate}
\inputminted[fontsize=\footnotesize, linenos]{dart}{./dart/graph/dot_manip.dart}
\subsubsection{Класс для графов}
Класс \textbf{Graphs} используется для хранения точек и манипуляции с ними.
Основная информация:
\inputminted[fontsize=\footnotesize, linenos]{dart}{./dart/graph/graph.dart}
\textit{String \_name} исользуется для имени графа, \textit{int \_amount} --- количество вершин, \textit{List<Dot> \_dots} --- массив точек, \textit{Map<int, String> \_nameTable} --- отслеживание имени каждой вершины, \textit{bool \_useLength} --- отслеживание взвешенности графа, \textit{bool \_oriented} --- отслеживание ориентированности графа.
Для работы графом доступны функции:
\begin{enumerate}
\item \textbf{String? addDot(Dot a)} --- добавление вершины;
\item \textbf{String? addIsolated(String name)} --- добавление изолированной точки;
\item \textbf{String? addPath(int from, int to, [int len = 0])} --- добавление пути;
\item \textbf{String? delPath(int from, int to)} --- удаление пути;
\item \textbf{String? delDot(int inn)} --- удаление вершины;
\item \textbf{void flushData()} --- удаление информации из графа;
\item \textbf{bool checkDots([bool verbose = false])} --- проверка графа на ошибки;
\item \textbf{void \_fixAfterDel(int inn}) --- испралвение нумерации после удаления;
\item \textbf{void \_syncNameTable()} --- синхронизация таблицы имен вершин;
\item \textbf{void setName(String name)} --- изменение имени;
\item \textbf{String? replaceDataFromFile(String path)} --- заменяет информацию графа на информацию из файла;
\item \textbf{List<LenDotPath> getSortedPathList()} --- возвращает список всех путей, отсортированный в порядке неубывания;
\item \textbf{void printG()} --- выводит информацию о графе в консоль;
\item \textbf{void printToFile(String name)} --- выводит информацию о графе в файл;
\end{enumerate}
Создать граф можно тремя способами:
\begin{enumerate}
\item пустой граф;
\item из списка смежности;
\item из файла.
\end{enumerate}
\subsubsection{Алгоритмы}
Алгоритмы описаны в классе графа в приложении~\ref{graph} .\newline
Обход в ширину:
\inputminted[fontsize=\footnotesize, linenos]{dart}{./dart/graph/bfs.dart}
Обход в глубину:
\inputminted[fontsize=\footnotesize, linenos]{dart}{./dart/graph/dfs.dart}
Алгоритм Дейкстры:
\inputminted[fontsize=\footnotesize, linenos]{dart}{./dart/graph/dijkstra.dart}
Алгоритм Краскала:
\inputminted[fontsize=\footnotesize, linenos]{dart}{./dart/graph/kruscal.dart}
\subsubsection{Интерфейс}
Интерфейс описан в файле drawing\_page.dart. Полный код доступен в приложении~\ref{buttons}.
Интерфейс программы изображен на рисунке~\ref{fig:programm}.
\begin{figure}[!ht]
\centering
\includegraphics[width=13cm]{./pic/grafs.png}
\caption{\label{fig:programm}
Интерфейс программы}
\end{figure}
\newpage
В качестве основы выбран виджет, сохраняющий состояние.
Построение интерфейса:
\inputminted[fontsize=\footnotesize, linenos]{dart}{./dart/page/build.dart}
Запуск работы алгоритмов происходит по нажатию соответствующих кнопок, затем страница отрисовки, используя полученные данные, выводит на экране информацию.
\subsubsection{Отрисовка графа}
Отрисовка элементов графа описана в файле curve\_painter.dart. Код доступен в приложении~\ref{painter}.
Для отрисовки информации используется виджет CustomPaint~\cite{custompaint}.
Для передачи информации графа используется конструктор CurvePainter.
\inputminted[fontsize=\footnotesize, linenos]{dart}{./dart/painter/constr.dart}
Отрисовка соединений происходит с помощью пакета ''arrow\_path''.
\inputminted[fontsize=\footnotesize, linenos]{dart}{./dart/painter/arrow.dart}
Для упрощения отрисовки, точки располагаются на окружности с центом, совпадающим с центром экрана. Сначала вычисляется их местоположение на экране, точка с максимальным количеством соединений располагается в центре:
\inputminted[fontsize=\footnotesize, linenos]{dart}{./dart/painter/pos.dart}
Процесс вывода на экран:
\begin{enumerate}
\item вывод вершин на экран;
\item определение текущей операции и вывод работы текущего алгоритма;
\item вывод ребер;
\item вывод длин ребер;
\item вывод имен вершин.
\end{enumerate}
\subsubsection{Итоговый вид приложения}
Итоговый вид приложения отображен на картинке~\ref{fig:result}
\begin{figure}[!ht]
\centering
\includegraphics[width=15cm]{./pic/result.png}
\caption{\label{fig:result}
Интерфейс программы}
\end{figure}
\newpage
% Раздел "Заключение"
\conclusion
Было разработано простое приложение для создания и работы с графами с использованием Dart и Flutter.
В приложении возможно с помощью графического интерфейса:
\begin{itemize}
\item создать пустой граф;
\item добавить вершину;
\item удалить вершину;
\item добавить/изменить путь;
\item удалить путь;
\item сохранить граф;
\item загрузить граф;
\item построить минимальное остовное дерево с помощью алгоритма Краскала;
\item найти минимальный путь из выбранной вершины в другие с помощью алгоритма Дейкстры;
\item проверить доступность вершин с помощью обхода в глубину;
\item построить путь из одной вершины в другую с помощью обхода в ширину.
\end{itemize}
%Библиографический список, составленный с помощью BibTeX
%
\bibliographystyle{gost780uv}
\bibliography{thesis}
% Окончание основного документа и начало приложений
% Каждая последующая секция документа будет являться приложением
\appendix
\section{Код main.dart}\label{mainD}
Точка входа в программу.
\inputminted[fontsize=\footnotesize, linenos]{dart}{./lib/main.dart}
\section{Код страницы отрисовки}\label{buttons}
Описание интерфейса и базовые функции для взаимодействия с информацией.
\inputminted[fontsize=\footnotesize, linenos]{dart}{./lib/pages/drawing_page.dart}
\section{Код отрисовки графа}\label{painter}
Вывод на экран информации из графа.
\inputminted[fontsize=\footnotesize, linenos]{dart}{./lib/src/curve_painter.dart}
\section{Код класса для работы с графом}\label{graph}
Основной класс для хранения и взаимодействия с информацией.
\inputminted[fontsize=\footnotesize, linenos]{dart}{./lib/src/graph.dart}
\noindent
\end{document}