# Структурирование программ ## Цель работы 1. Уметь определять структуры. 1. Уметь структурировать программу при помощи функций. 1. Уметь писать программы из нескольких единиц трансляции. 1. Уметь писать модульные тесты. ## Задание Работа ведется на основе кода лабораторной работы № 1. 1. Структурировать программу при помощи функций: * Определить структуру `Input` для хранения исходных данных. * Вынести ввод данных в функцию `input_data()`. * Вынести поиск минимума и максимума в функцию `find_minmax()`. * Вынести расчет количества чисел в корзинах в функцию `make_histogram()`. * Вынести отображение гистограммы в функцию `show_histogram_text()`. 1. Разделить программу на единицы трансляции: * `main.cpp`: основная программа; * `histogram.cpp`: функции для расчетов; * `text.cpp`: отображение гистограммы в виде текста. 1. Написать программу с модульными тестами функции `find_minmax()`. 1. Перевести программу на отображение гистограммы в формате SVG. В результате должно быть две программы с частично общим кодом. Основная программа при запуске без параметров работает так же, как ЛР № 1 в базовом виде (не своего варианта), но выводит гистограмму в формате SVG. Вторая программа выполняет модульные тесты функции `find_minmax()`. Код должен быть загружен в репозитарий `cs-lab34`. Начальный коммит должен содержать код ЛР № 1 без изменений. Должны быть коммиты, фиксирующие выполнение пунктов задания, с номером и описанием пункта в первой строке сообщения к коммиту. Можно делать больше промежуточных коммитов на свое усмотрение. Отчета не нужно. ## Указания к выполнению ### 1. Импорт кода ЛР № 1 в Git 1. В каталоге с файлами ЛР № 1 инициализируйте репозитарий Git. 1. Настройте свое имя пользователя и почту МЭИ. 1. Закоммитьте файл исходного кода (`*.cpp`) и проекта (`*.cbp` для CodeBlocks). 1. Настройте игнорирование артефактов сборки (`bin/` и `obj/` для CodeBlocks). 1. Создайте пустой репозитарий `cs-lab34` на Git УИТ. 1. Загрузите код на сервер (при необходимости настройте доступ по SSH). ### 2. Структурирование программы при помощи функций Программа для построения гистограммы из ЛР № 1 состоит из одной функции `main()` на более чем 100 строк, из-за чего в ней неудобно ориентироваться. #### Структура для входных данных Входные данные включают вектор чисел и количество корзин: ```cpp struct Input { vector numbers; size_t bin_count{}; }; ``` Если ваш вариант ЛР № 1 требует ввода дополнительных данных, на этом этапе удаляйте весь специфичный для варианта код. Когда будете выполнять индивидуальный вариант к этой ЛР, добавьте в эту структуру дополнительные поля входных данных, если вариант того требует. Поля примитивных типов (не `vector` и не `string`) рекомендуется инициализировать нулевыми значениями, как это сделано для `bin_count`. Тогда при объявлении переменной типа `Input` без инициализации у полей будут удобные нулевые значения по умолчанию. #### Функция ввода данных Код для ввода данных в базовом варианте: ``` cpp size_t number_count; cin >> number_count; vector numbers(number_count); for (size_t i = 0; i < number_count; i++) { cin >> numbers[i]; } size_t bin_count; cin >> bin_count; ``` Требуется вынести его в функцию `input_data()`, чтобы в `main()` вместо строк выше осталась одна: ```cpp Input in = input_data(); ``` Функция не принимает параметров, а возвращает структуру `Input`: ```cpp Input input_data() { ``` Предлагается в ЛР писать тип возвращаемого значения на отдельной строке. В общем случае это регламентируется стилем кодирования в проекте. Количество чисел не входит напрямую в `Input` — входит вектор чисел, который уже несет в себе количество элементов. Тем не менее, ввод количества нужен для того, чтобы задать размер вектора. Поэтому `number_count` объявляется как локальная переменная и вводится так же, как вводилась в функции `main()`: ```cpp size_t number_count; cin >> number_count; ``` Далее вводятся те данные, которые нужно будет возвращать из функции. Это поля структуры `Input`, поэтому нужно объявить переменную-экземпляр: ```cpp Input in; ``` По умолчанию вектор чисел `in.numbers` инициализируется пустым, а количество корзин `in.bin_count` инициализируется нулем. Нужно изменить размер вектора на введенный: ```cpp in.numbers.resize(number_count); ``` Элементы `in.numbers` вводятся так же, как элементы `numbers` вводились в `main()`: ```cpp for (size_t i = 0; i < number_count; i++) { cin >> in.numbers[i]; } ``` **Самостоятельно.** Напишите ввод `in.bin_count`. Если ваш вариант этой ЛР потребует ввода дополнительных данных, нужно будет добавлять его в эту часть, когда будете делать индивидуальную часть. В конце функции нужно вернуть результат: ```cpp return in; } ``` На этом этапе структура кода должна получаться такой: ``` cpp #include ... struct Input { ... }; Input input_data() { ... } int main() { Input in = input_data(); ... } ``` #### Функция поиска минимума и максимума При поиске минимума и максимума результатов два. Оператор `return` не позволяет вернуть два значения, если только они не упакованы в одно составное, например, в структуру. Однако для тренировки используем выходные параметры: ``` cpp double min, max; find_minmax(in.numbers, min, max); ``` Нужно сделать так, чтобы этот код работал следующим образом: в теле функции `find_minmax()` изменялись бы аргументы `min` и `max`, и эти изменения должны отразиться на переменных `min` и `max` в функции `main()`. Предположим, функция определена так: ``` cpp void find_minmax(vector numbers, double min, double max) { min = numbers[0]; // (здесь код поиска минимума и максимума) } ``` Желаемого поведения не будет: параметры `min` и `max` в функции — это отдельные переменные, хотя они и называются так же, как переменные в `main()`. Когда функция `find_minmax()` вызывается, параметры `min` и `max` получают *значения*, которым были равны второй и третий аргумент (говорят: передаются по значению). Любые изменения `min` и `max` в теле функции не отразятся на аргументах. Чтобы из `find_minmax()` менять внешние переменные, можно использовать указатели или ссылки. С точки зрения организации выходных параметров, указатели отличаются тем, что у них есть особое значение, нулевой указатель (`nullptr`/`0`/`NULL`/`{}`). Однако в данном случае мы не заинтересованы в том, чтобы такое значение было: функция всегда ищет минимум и максимум и всегда должна мочь их записать. Используем ссылки (см. амперсанды после `double`): ``` cpp void find_minmax(vector numbers, double& min, double& max) { min = numbers[0]; // (здесь код поиска минимума и максимума) } ``` Выделите `find_minmax()`, замените код в `main()` её вызовом. Добейтесь компиляции программы и проверьте её работу. Оптимально ли выбран тип входного параметра `numbers`? Во-первых, изменять его не требуется, он может быть константным. Во-вторых, поскольку он не объявлен ссылкой или указателем, это отдельная переменная, которая получает значением копию вектора, который передается в функцию. Однако копия не нужна. Итого выгоднее использовать ссылку, чтобы не было копирования, но константную, чтобы пользователь функции (программист) понимал, что она не изменяет этот вектор: `const vector& numbers`. Измените тип параметра `numbers` функции `find_minmax()`. Проверьте, что программа компилируется, а ее поведение не изменилось. #### Функция расчета гистограммы Функция `make_histogram()` принимает вектор чисел и количество корзин. Она возвращает количества чисел в корзинах, то есть вектор `bins`. В своей работе она вызывает `find_minmax()`. #### Прочие функции В итоге структура кода в `main()` должна получиться такой: ```cpp int main() { auto in = input_data(); auto bins = make_histogram(in.numbers, in.bin_count); show_histogram_text(bins, ...); } ``` Если ваш вариант ЛР № 1 требует дополнительных данных для вывода, на этом этапе удаляйте весь специфичный для варианта код. Добейтесь корректной работы программы и сделайте коммит. ### 3. Разделение программы на файлы Хорошей практикой является отделять часть программы, которая выполняет расчеты, от частей, которые занимаются вводом и выводом. Разделим программу на файлы так: ``` +-----------------------------+ | histogram.h | +-----------------------------+ | объявление make_histogram() | +-----------------------------+ ↑ ↑ включает файл, включает файл, вызывает функцию реализует функцию ↑ ↑ +------------------+ +------------------+ | main.cpp | | histogram.cpp | +------------------+ +------------------+ | вызов | | определение | | make_histogram() | | make_histogram() | +------------------+ +------------------+ ``` #### Создание заголовочного файла При помощи меню *File → New → File...* добавьте к проекту заголовочный файл *(C/C++ header).* Он должен быть расположен в каталоге проекта: *Filename with full path:* `C:\cs-lab34\histogram.h` \ *Add file to active project:* (отмечена) **Примечание.** Здесь и далее каталог проекта обозначен как `C:\cs-lab34`. Всюду нужно заменять его на тот, который реально используется. Заготовка файла включает «стража включения» (см. лекцию): ```cpp #ifndef HISTOGRAM_H_INCLUDED #define HISTOGRAM_H_INCLUDED #endif // HISTOGRAM_H_INCLUDED ``` Можно заменить его на более простой вариант `#pragma once` или писать код в этом файле между `#define` и `#endif`. Добавьте в этот файл объявление `make_histogram()`: ```cpp #include std::vector make_histogram(const std::vector& numbers, size_t bin_count); ``` В заголовочных файлах не рекомендуется писать `using namespace std;`, потому что в файле реализации, куда этот заголовочный файл подключается, нет возможности отменить эту директиву, а она нужна на всегда. Поэтому в примере `std::vector` используется с указанием пространства имен. #### Создание файла реализации 1. При помощи меню *File → New → File...* добавьте к проекту файл реализации *(C/C++ source).* Необходимые настройки: *Filename with full path:* `C:\cs-lab34\histogram.cpp` \ *Add file to active project in build target(s)*: нажать *All*, либо проставить флажки в пунктах *Debug* и *Release* 1. Перенесите определения `find_minmax()` и `make_histogram()` в `histogram.cpp`. 1. Сделайте функцию `find_minmax()` статической, так как она не нужна за пределами `histogram.cpp`. 1. Подключите `histogram.h` в `main.cpp` и `histogram.cpp`: ```cpp #include "histogram.h" ``` Нужно использовать двойные кавычки, так как файл находится не в стандартном каталоге, а ищется относительно файла `*.cpp`. 1. Убедитесь, что проект собирается и запускается. **Самостоятельно.** Выделите `show_histogram_text()` в `text.h` и `text.cpp`. ### 4. Модульные тесты Ранее для тестирования программы применялось перенаправление ввода и вывода. Такое тестирование называется функциональным. Однако функционального тестирования недостаточно для больших программ. Предположим, большая программа перестала выдавать правильный результат — как найти место ошибки? Необязательно это последнее изменение: возможно, другая часть и ранее работала неверно в некоторых случаях, и последняя правка лишь обнажила проблему. Модульное тестирование (unit testing) проверяет не работу всей программы, а работу отдельных ее компонент, например, отдельных функций. Модульные тесты пишутся программистами для собственного кода. Функциональные тесты могут писаться или проводится вручную другими специалистами. Модульный тест — это отдельная программа, которая изолированно проверяет части кода основной программы. Если желательно протестировать части сложного алгоритма, эти части должны быть оформлены в виде отдельных функций (говорят: код должен быть *тестируемым).* #### Как сделать код тестируемым? Функция `find_minmax()`, которую нужно протестировать, скрыта в `histogram.cpp`. Даже если разместить в другом файле её объявление, компоновка не прошла бы, потому что эта функция статическая. Это довольно типичная ситуация, когда желательно протестировать функции, которые нет смысла описывать в интерфейсе модуля. Она означает, что модуль достаточно сложный, и имеет смысл выделить его части в подмодули. С другой стороны, если перенести `find_minmax()` в отдельный файл, читать код станет только сложнее — придется переходить по файлам. Поэтому можно только объявление тестируемой функции описать в отдельном файле. Подключив его, к ней можно будет обращаться из теста, а сама функция будет в `histogram.cpp`, чтобы код оставался понятным. 1. Сделайте функцию `find_minmax()` нестатической. 1. Добавьте файл `histogram_internal.h` с объявлением `find_minmax()`. #### Создание проекта для модульных тестов 1. При помощи меню *File → New → Project...* создайте новый проект типа *Empty project.* **Важно.** Не выбирайте *Console application,* иначе содержимое `main.cpp` будет заменено на пустую заготовку программы. Необходимые настройки: *Project title:* `unittest` \ *Folder to create project in:* `C:\cs-lab34` \ *Project filename:* `unittest.cbp` \ *Resulting filename:* `C:\cs-lab34\unittest.cbp` В последнем параметре подкаталога `unittest` быть не должно. 1. Дважды щелкните по проекту `unittest`, чтобы сделать его активным. 1. Из контекстного меню проекта `unittest` выберите пункт *Add files...* и добавьте к проекту `histogram_internal.h` и `histogram.cpp`. В открывшемся диалоге *Select the targets this file should belong to:* проставьте все флажки. 1. При помощи меню *File → New → File...* добавьте к проекту `unittest` файл реализации *(C/C++ source)* `unittest.cpp`. Необходимые настройки: *Filename with full path:* `C:\cs-lab34\unittest.cpp` \ *Add file to active project in build target(s)* — нажать *All*, либо проставить флажки в пунктах *Debug* и *Release* #### Подключение библиотеки для модульного тестирования Модульные тесты представляют собой проверки утверждений о том, как ведет себе код при различных условиях. Технически это набор коротких функций, каждая их которых: 1. Готовит условия, например, формирует исходные данные: ```cpp vector numbers{1, 2}; ``` 2. Вызывает тестируемый код, например: ```cpp double min, max; find_minmax(numbers, min, max); ``` 3. Выполняет ряд проверок, например: ```cpp if (min != 1) { puts("min != 1"); } if (max != 2) { puts("max != 2"); } ``` Важно, чтобы последний пункт в случае ошибок давал внятную диагностику: в каком тесте какая проверка не прошла, какое значение было ожидаемым, какое актуальное значение выдала функция. Печатать все эти сведения вручную для каждого теста накладно и чревато ошибками. Поэтому модульные тесты пишут с использованием библиотек. Мы предлагаем использовать библиотеку `doctest`, потому что её очень просто подключить к проекту — это один заголовочный файл. На практике самой популярной является Google Test, но её сложнее подключать. В целом, все такие библиотеки похожи, но отличаются продвинутыми возможностями. Загрузите [doctest.h][doctest/doctest.h] и сохраните в каталоге проекта. Закоммитьте этот файл. [doctest/doctest.h]: https://raw.githubusercontent.com/doctest/doctest/master/doctest/doctest.h #### Написание модульных тестов Напишем тест для простейшего случая в файле `unittest.cpp`: ```cpp #define DOCTEST_CONFIG_NO_MULTITHREADING #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include "doctest.h" #include "histogram_internal.h" TEST_CASE("distinct positive numbers") { double min = 0; double max = 0; find_minmax({1, 2}, min, max); CHECK(min == 1); CHECK(max == 2); } ``` Функцию `main()` писать не нужно — doctest автоматически определит её. Каждый тест должен определяться через макрос `TEST_CASE()`, как показано выше. В скобках описывается смысл теста, doctest будет показывать его в сообщениях. Тест всегда включает один или несколько вызовов макроса `CHECK()`. При ошибках doctest покажет, какая из проверок не сработала, и всю дополнительную информацию. [Вводная документация doctest.][doctest/tutorial] [doctest/tutorial]: https://github.com/doctest/doctest/blob/master/doc/markdown/tutorial.md Соберите проект модульных тестов, запустите его и убедитесь, что он работает без ошибок. Проверим, что будет, если тест выявляет ошибку. Замените `CHECK(min == 1)` на `CHECK(min == 3)`, соберите проект и запустите его. Верните код к правильному варианту. **Самостоятельно.** Придумайте больше тестовых случаев и напишите для них тесты. В частности, можно проверить случаи: * пустого вектора * вектора с одним элементом * вектора отрицательных элементов * вектора из одинаковых элементов ### 5. Вывод гистограммы как изображения в формате SVG Требуется вместо текстовой гистограммы рисовать картинку, например: ![Пример изображения-результата.](assets/histogram.svg) Это изображение описывается таким текстом, который должна выводить программа: ```xml 2 5 3 ``` Первые две строки и последняя строка — фиксированные. Строки с отступом — это подписи к столбцам (элементы `text`) и столбцы в виде цветных прямоугольников (**rect**angle, элементы `rect`). На каждый столбец программа должна печатать строку ` Текст **с полужирными** словами и [ссылкой](#) в никуда. Бразуер отображает его из следующего фрагмента разметки (язык разметки называется HTML, hyper-text markup language): ```html

Текст с полужирными словами и ссылкой в никуда.

``` Всегда можно нажать *Ctrl+U* и увидеть исходный код страницы (разметку). SVG работает так же, только разметка описывает не текст с оформлением, а изображение. HTML указывает, как отображать определенные блоки текста, а SVG указывает, какие геометрические фигуры нарисовать. Если щелкнуть правой кнопкой мыши по изображению выше и выбрать «Показать изображение», оно откроется отдельно. Тем же *Ctrl+U* можно посмотреть код разметки гистограммы, который в итоге будет выводить программа. Если если программа будет печатать размету SVG вместо звездочек, можно перенаправить вывод программы в файл с расширением `*.svg` и открыть его браузером — будет нарисовано изображение. В случае ошибки оно нарисовано не будет. В этом случае можно *Ctrl+U* посмотреть, что программа вывела не так. Как правило, в окне кода, которое открывается по *Ctrl+U,* красным цветом выделяются синтаксические ошибки, то есть некорректная разметка. ## Формат SVG Разберем заголовок SVG: ```xml ``` Так описывается изображение размером 300×100 точек. Система координат SVG отличается от математической: ось Y направлена вниз, то есть точка *(0,0)* находится в верхнем левом углу. Координаты в SVG действительные, то есть может быть точка *(0.5, 3.14).* Фрагменты вида ``, `` или `` называются *элементами.* Параметры вида `height='300'` называются *атрибутами* элементов. Элементы имеют открывающий тэг (``), закрывающий тэг (``) с косой чертой и содержимое между ними (`содержимое`). Если содержимого нет, то тэг может быть сразу закрыт: ``. Подробнее об SVG можно прочитать [в спецификации](https://developer.mozilla.org/en-US/docs/Web/SVG/Element), но все нужные для ЛР сведения даны в этих указаниях. SVG поддерживает множество элементов, среди них: * `anything you want`: текст «anything you want», левый нижний угол которого в точке *(20,35);* * ``: прямоугольник 100×200 с верхним левым углом в точке *(0,0)*. #### Цикл отладки функций вывода SVG Вывод отдельных элементов SVG (заголовка, окончания, текста, прямоугольника) стоит оформить в виде функций, который будут принимать параметры геометрии и печатать соответствующий текст. Функция `show_histogram_svg()` будет выводить гистограмму в формате SVG. Функции для работы с SVG рекомендуется выделить в модуль `svg.h`/`svg.cpp`. Отработаем цикл модификации и проверки программы. Функции вывода заголовка и окончания SVG: ``` cpp void svg_begin(double width, double height) { cout << "\n"; cout << "\n"; } void svg_end() { cout << "\n"; } ``` Обратите внимание на пробелы в строках, например, перед закрывающей кавычкой во фрагменте `"viewBox='0 0 "`. Они необходимы между атрибутами. Также обратите внимание на использование одинарных кавычек. Двойные кавычки ограничивают в C++ строковые литералы; одинарные кавычки внутри них выводятся «как есть» в результирующую строку. Путаница кавычек или отсутствие некоторых пробелов сделает SVG некорректным. Пусть начальная реализация графического вывода гистограммы всегда выводит пустое изображение фиксированного размера: ``` cpp void show_histogram_svg(const vector& bins) { svg_begin(400, 300); svg_end(); } ``` Заменим вызов `show_histogram_text(bins)` вызовом `show_histogram_svg(bins)`. Создать файл изображения можно так (`marks.txt` можно [скачать](assets/marks.txt) или создать): ``` sh C:\cs-lab34\bin\Debug> cs-lab34.exe marks.svg ``` Эту команду нужно вводить в консоли, открытой в папке с exe-файлом проекта. Если проект называется не `cs-lab34`, название файла нужно написать свое. Просмотреть, какие файлы есть в текущем каталоге, можно командой `dir`. Чтобы быстро просмотреть из консоли содержимое `marks.svg` (разметку), можно использовать команду: ```sh C:\cs-lab34\bin\Debug> type marks.svg ``` В браузере `marks.svg` открывается из меню *Файл → Открыть...* Отобразится пустая страница (пустой рисунок). Можно нажать *Ctrl+U* или пункт *«Исходный код страницы»* в контекстном меню любого места страницы, чтобы увидеть результирующий код SVG (контекстное меню — это то, что появляется по щелчку правой кнопкой мыши в любом пустом месте страницы). #### Функции вывода элементов SVG Для вывода подписей к столбцам напишем функцию вывода текста в SVG, которая принимает координату по горизонтали (`left`), координату нижнего края текста по вертикали (`baseline`) и сам текст: ``` cpp void svg_text(double left, double baseline, string text); ``` Из введения в SVG выше известно, что она должна выводить строку такого формата: ``` xml anything you want ``` На C++ точно такую строку можно вывести следующим образом (`svg_text()`) должна размещаться выше `show_histogram_svg()`: ``` cpp void svg_text(double left, double baseline, string text) { cout << "anything you want"; } ``` Чтобы вместо координаты `x` (числа 20) выводить значение `left`: ``` cpp cout << "anything you want"; ``` Обратите внимание на то, что сохранены одинарные кавычки внутри строк C++ (в двойных кавычках) — они необходимы по правилам SVG. **Самостоятельно.** Закончите реализацию `svg_text()`, чтобы подставлять значение координаты `baseline` и текст надписи `text`. Для проверки выведем высоту первого столбца гистограммы: ``` cpp void show_histogram_svg(const vector& bins) { svg_begin(400, 300); svg_text(20, 20, to_string(bins[0])); svg_end(); } ``` Функция `to_string()` преобразует значения разных типов в строки. Чтобы проверить модифицированную программу, её нужно запустить повторно: ``` sh C:\cs-lab34\bin\Debug> cs-lab34.exe marks.svg ``` После повторного запуска программы открытый в браузере файл можно обновить клавишей *F5.* На странице должен быть виден текст «2». Если в файле ошибка, он не отобразится. В этом случае нужно просмотреть код страницы на предмет ошибок в SVG, исправить код программы, перезапустить ее и проверить результат. **Самостоятельно.** Напишите функцию для вывода прямоугольника в SVG: ``` cpp void svg_rect(double x, double y, double width, double height); ``` Для проверки выведем первый столбец гистограммы справа от подписи к нему: ``` cpp svg_rect(50, 0, bins[0] * 10, 30); ``` Убедитесь, что вывод первого столбца гистограммы работает. #### Исключение «магических констант» Текст выводится в координатах *(20, 20)*, смещение левого края столбца *50* (оно же — ширина подписей), а высота столбца *30.* Эти числа подобраны так, чтобы элементы гистограммы не накладывались друг на друга, но они разбросаны по коду и их смысл неясен. Прямо внутри функции `show_histogram_svg()` стоит завести константы, чтобы пользоваться ими при выводе: ``` cpp const auto IMAGE_WIDTH = 400; const auto IMAGE_HEIGHT = 300; const auto TEXT_LEFT = 20; const auto TEXT_BASELINE = 20; const auto TEXT_WIDTH = 50; const auto BIN_HEIGHT = 30; const auto BLOCK_WIDTH = 10; ``` #### Вывод гистограммы Логика вывода гистограммы следующая: каждая корзина выводится так же, как первая, но к вертикальной координате добавляется смещение — высота столбца: ``` cpp double top = 0; for (size_t bin : bins) { const double bin_width = BLOCK_WIDTH * bin; svg_text(TEXT_LEFT, top + TEXT_BASELINE, to_string(bin)); svg_rect(TEXT_WIDTH, top, bin_width, BIN_HEIGHT); top += BIN_HEIGHT; } ``` ### Оформление гистограммы. Значения параметров по умолчанию Черная гистограмма не слишком эстетична или экономична при печати. За цвет линий в SVG отвечает атрибут `stroke`, а за цвет заливки — `fill`. Можно задать один из [стандартных цветов][svg/colors] или выбрать цвет в формате `#RRGGBB` из [палитры][svg/rgb]. Пример прямоугольника с красными границами и бледно-розовой заливкой: ``` xml ``` [svg/colors]: https://www.december.com/html/spec/colorsvghex.html [svg/rgb]: http://getcolor.ru Функция `svg_rect()` может быть доработана для указания цвета линий и заливки: ``` cpp void svg_rect(double x, double y, double width, double height, string stroke, string fill); ``` **Самостоятельно.** Измените цвета вывода и проверьте работу программы. #### Значения параметров по умолчанию Цвета элементов нужно задавать не всегда. В текущей реализации у функции `svg_rect()` шесть параметров, два из которых отвечают за цвет. Можно было бы перегрузить функцию: ``` cpp void svg_rect(double x, double y, double width, double height); void svg_rect(double x, double y, double width, double height, string stroke, string fill); ``` Однако в этом случае пришлось бы в каждой функции писать похожий код. В данном случае выгоднее применить значения параметров по умолчанию: ``` cpp void svg_rect(double x, double y, double width, double height, string stroke = "black", string fill = "black"); ``` Значения по умолчанию указываются только один раз в объявлении функции (если объявление и определение отделены). Новая версия `svg_rect()` работает так: ``` cpp svg_rect(0, 0, 100, 200); // svg_rect(0, 0, 100, 200, "black", "black"); svg_rect(0, 0, 100, 200, "red"); // svg_rect(0, 0, 100, 200, "red", "black"); svg_rect(0, 0, 100, 200, "blue", "#aaffaa"); ``` **Самостоятельно.** Реализуйте для гистограммы в SVG масштабирование: самый длинный столбец должен занимать `(IMAGE_WIDTH - TEXT_WIDTH)` точек. ## Индивидуальные задания Результат выполнения нужно оформить отдельным коммитом на GitHub. Доработку следует делать с использованием функции, для который нужно добавить unit-тесты из не менее двух существенно отличающихся случаев. **Пример 1.** По заданию нужно запросить у пользователя цвет заливки столбцов, а если введен красный, отказаться его использовать и запросить повторно. Можно выделить функцию `bool check_color(string color)`, которая принимает введенную пользователем строку цвета и возвращает `true`, если цвет подходящий (не красный). Для нее можно придумать положительный тест, что `check_color("blue") == true` и отрицательный, что `check_color("red") == false`. **Пример 2.** По заданию требуется добавить гистограмме заголовок *Гистограмма* по центру. Можно выделить функцию, которая принимает ширину и высоту изображения и вычисляет координаты надписи. Тестовые случаи — горизонтальное и вертикальное изображение (ширина больше или меньше высоты соответственно). В вариантах примеры приведены для текстовых гистограмм, но решение должно выдавать SVG. #### Вариант 1 Дайте пользователю возможность задавать произвольную ширину столбца гистограммы вместо 400. Считайте некорректной ширину менее 70, более 800 или менее трети количества чисел, умноженных на ширину блока (`BLOCK_WIDTH`) — предлагайте пользователю ввести ширину заново с указанием причины. #### Вариант 2 Задавать автоматически яркость заливки каждого столбца гистограммы в градациях серого в зависимости от высоты столбца. Чем больше столбец, тем темнее заливка. Сделать это можно, передавая цвет в параметр fill в формате `"#RGB"` ([red, green, blue](http://htmlbook.ru/html/value/color)). `"#111"` — самый темный, `"#222"` — чуть менее темный, ..., `"#EEE"` — практически белый, `"#FFF"` — белый. В лабораторной работе использовать диапазон цветов от `"#111"` для самого большого столбца до `"#999"` для самого маленького столбца. Поскольку используются градации серого, расчет сводится к вычислению только одного значения и дублированию этого значения в качестве цвета каждого из каналов (Red, Green, Blue). Для расчета цвета *i*-го столбца `bins[i]` использовать формулу `(10 - (bins[i] * 9) / max_count)`. По ней мы получаем значение цвета одного канала (от 1 до 9), который затем записываем три раза. Пример с текстом вместо SVG: `1|``▮` — цвет `#999` \ `5|``▮▮▮▮▮` — цвет `#111` \ `3|``▮▮▮` — цвет `#555` #### Вариант 3 Измените высоту изображения `IMAGE_HEIGHT = 700`. Дайте пользователю возможность задавать высоту столбца гистограммы. Если итоговая высота гистограммы больше `IMAGE_HEIGHT`, рассчитывать высоту столбца как `(IMAGE_HEIGHT / bins_count)`. #### Вариант 4 Подсчитайте процент элементов, попавших в столбец, как целое двузначное число с `%` в конце и отображайте этот процент после столбца гистограммы с выравниванием: ``` 3|*** 25% 5|***** 42% 4|**** 33% ``` #### Вариант 5 Отображайте гистограмму зеркально по аналогии с заданием этого варианта в лабораторной работе № 1. #### Вариант 6 После запроса количества столбцов запросите цвет для каждого столбца. #### Вариант 7 Вычислите среднюю высоту столбца. Если столбец выше, цвет столбца должен быть красным, если ниже или равен средней высоте, цвет зеленый. #### Вариант 8 Запрашивать у пользователя размер шрифта. За размер шрифта отвечает атрибут `font-size`. Считать 12 значением по умолчанию. Не позволять вводить значения менее 8 и более 32. В этом случае предлагайте пользователю ввести значение заново с указанием причины. #### Вариант 9 Запросите у пользователя ширину одного «блока» гистограммы `BLOCK_WIDTH`. Не позволяйте вводить ширину блока менее 3px и более 30px. В этом случае предлагайте пользователю ввести ее заново с указанием причины. #### Вариант 10 Отображайте гистограмму вертикально, с подписями сверху, по аналогии с заданием этого варианта в лабораторной работе 1. Предусмотреть расчет `IMAGE_HEIGHT` таким образом, чтобы вся гистограмма вмещалась в область рисунка. #### Вариант 11 Добавьте рамку вокруг гистограммы, используя пунктирные линии. Для отрисовки пунктирной линии можно использовать стандартный элемент ``, установив в нем атрибут `stroke-dasharray = '10 10'` #### Вариант 12 Добавьте на ось подписей границы столбцов по аналогии с заданием этого варианта в лабораторной работе 1. #### Вариант 13 Отображайте гистограмму вертикально с подписями снизу. ``` * * * * * * * * * _ _ _ 3 5 1 ``` Предусмотреть расчет `IMAGE_HEIGHT` таким образом, чтобы вся гистограмма вмещалась в область рисунка. #### Вариант 14 Разделяйте каждый столбец пунктирными линиями длиной `IMAGE_WIDTH`. Запрашивайте у пользователя шаблон пунктира. Шаблон пунктира задается в атрибуте `stroke-dasharray` блока `` в виде `stroke-dasharray = '20 10'`, где 20 означает длину черточки, 10 - длину промежутка. У пользователя нужно запросить как длину черточки, так и длину промежутка. #### Вариант 15 Добавьте горизонтальную шкалу под гистограммой по аналогии с заданием лабораторной работы 1. Шкалу нужно разбить на интервалы, размер которых вводит пользователь. Допустимы размеры от 2 до 9 `BLOCK_WIDTH`, при некорректном вводе печатайте сообщение со словом «ERROR» и завершайте работу программы. Под нулевой, первой и последней отметкой шкалы требуется напечатать соответствующие числа. Шкала должна быть во всю ширину гистограммы. #### Вариант 16 После запроса количества столбцов запросить цвет линий для каждого столбца. Проверять ввод: цвет должен либо начинаться с `#`, либо не иметь внутри пробелов. #### Вариант 17 Задавать автоматически прозрачность заливки каждого столбца гистограммы в зависимости от высоты столбца. Чем больше столбец, тем темнее заливка. Сделать это можно, передавая процент прозрачности в параметр `fill-opacity` в формате `"0.7"`. 1 соответствует отсутствию прозрачности, 0 соответствует полной прозрачности (отсутствию цвета) Для расчета прозрачности каждого i-го столбца `bins[i]` использовать формулу `(bins[i]) / max_count)`. Пример: `1|``▮` — прозрачность 0.2 \ `5|``▮▮▮▮▮` — прозрачность 1.0 \ `3|``▮▮▮` — прозрачность 0.6 #### Вариант 18 Позволять пользователю делать оформление текста - подчеркивание, надчеркивание, ~~зачеркивание~~ текста. За оформление шрифта отвечает атрибут `text-decoration`. Сделать `'none'` значением по умолчанию. Допустимые значения: `none`, `underline`, `overline`, `line-through`. Проверять введенное пользователем значение, и если оно не одно из допустимых, запрашивать значение заново, выдавая предупреждение.