|
|
# Структурирование программ
|
|
|
|
|
|
## Цель работы
|
|
|
|
|
|
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<double> numbers;
|
|
|
size_t bin_count{};
|
|
|
};
|
|
|
```
|
|
|
|
|
|
Если ваш вариант ЛР № 1 требует ввода дополнительных данных,
|
|
|
на этом этапе удаляйте весь специфичный для варианта код.
|
|
|
Когда будете выполнять индивидуальный вариант к этой ЛР,
|
|
|
добавьте в эту структуру дополнительные поля входных данных,
|
|
|
если вариант того требует.
|
|
|
|
|
|
Поля примитивных типов (не `vector` и не `string`) рекомендуется
|
|
|
инициализировать нулевыми значениями, как это сделано для `bin_count`.
|
|
|
Тогда при объявлении переменной типа `Input` без инициализации
|
|
|
у полей будут удобные нулевые значения по умолчанию.
|
|
|
|
|
|
#### Функция ввода данных
|
|
|
|
|
|
Код для ввода данных в базовом варианте:
|
|
|
|
|
|
``` cpp
|
|
|
size_t number_count;
|
|
|
cin >> number_count;
|
|
|
|
|
|
vector<double> 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<double> 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<double> numbers, double& min, double& max) {
|
|
|
min = numbers[0];
|
|
|
// (здесь код поиска минимума и максимума)
|
|
|
}
|
|
|
```
|
|
|
|
|
|
Выделите `find_minmax()`, замените код в `main()` её вызовом.
|
|
|
Добейтесь компиляции программы и проверьте её работу.
|
|
|
|
|
|
Оптимально ли выбран тип входного параметра `numbers`?
|
|
|
Во-первых, изменять его не требуется, он может быть константным.
|
|
|
Во-вторых, поскольку он не объявлен ссылкой или указателем,
|
|
|
это отдельная переменная, которая получает значением копию вектора,
|
|
|
который передается в функцию.
|
|
|
Однако копия не нужна.
|
|
|
Итого выгоднее использовать ссылку, чтобы не было копирования,
|
|
|
но константную, чтобы пользователь функции (программист) понимал,
|
|
|
что она не изменяет этот вектор: `const vector<double>& 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 <vector>
|
|
|
|
|
|
std::vector<size_t>
|
|
|
make_histogram(const std::vector<double>& numbers, size_t bin_count);
|
|
|
```
|
|
|
|
|
|
В заголовочных файлах не рекомендуется писать `using namespace std;`,
|
|
|
потому что в файле реализации, куда этот заголовочный файл подключается,
|
|
|
нет возможности отменить эту директиву, а она нужна на всегда.
|
|
|
Поэтому в примере `std::vector<T>` используется с указанием пространства имен.
|
|
|
|
|
|
#### Создание файла реализации
|
|
|
|
|
|
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<double> 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
|
|
|
|
|
|
Требуется вместо текстовой гистограммы рисовать картинку, например:
|
|
|
|
|
|

|
|
|
|
|
|
Это изображение описывается таким текстом, который должна выводить программа:
|
|
|
|
|
|
```xml
|
|
|
<?xml version='1.0' encoding='UTF-8' ?>
|
|
|
<svg width='300' height='100' viewBox='0 0 300 100' xmlns='http://www.w3.org/2000/svg'>
|
|
|
<text x='20' y='20'>2</text>
|
|
|
<rect x='50' y='0' width='100' height='30' fill='#ffaaaa' />
|
|
|
<text x='20' y='50'>5</text>
|
|
|
<rect x='50' y='30' width='250' height='30' fill='#aaffaa' />
|
|
|
<text x='20' y='80'>3</text>
|
|
|
<rect x='50' y='60' width='150' height='30' fill='#aaaaff' />
|
|
|
</svg>
|
|
|
```
|
|
|
|
|
|
Первые две строки и последняя строка — фиксированные.
|
|
|
Строки с отступом — это подписи к столбцам (элементы `text`)
|
|
|
и столбцы в виде цветных прямоугольников (**rect**angle, элементы `rect`).
|
|
|
На каждый столбец программа должна печатать строку `<text...`
|
|
|
и строку `<rect...` со значениями `x`, `y`, `width` и `height`,
|
|
|
вычисленными в зависимости от номера столбца и количества попавших в него чисел.
|
|
|
|
|
|
#### Форматы разметки
|
|
|
|
|
|
Возьмем любую страницу в интернете, например, эту.
|
|
|
На ней присутствует визуальное оформление: заголовки крупные и жирные,
|
|
|
ссылки подчеркнуты и по ним можно перейти, между абзацами есть отступы.
|
|
|
Можно догадаться, что изображение на экране не приходит с сервера в готовом виде,
|
|
|
потому что страница выглядит по-разному на телефоне и на компьютере, например.
|
|
|
Страница передается в виде разметки — текста со специальными элементами —
|
|
|
на основании которой браузер рисует на экране один текст как заголовок,
|
|
|
второй — как ссылку, третий — отдельным абзацем.
|
|
|
|
|
|
Например, возьмем такой абзац:
|
|
|
|
|
|
> Текст **с полужирными** словами и [ссылкой](#) в никуда.
|
|
|
|
|
|
Бразуер отображает его из следующего фрагмента разметки
|
|
|
(язык разметки называется HTML, hyper-text markup language):
|
|
|
|
|
|
```html
|
|
|
<p>Текст <strong>с полужирными</strong> словами и <a href="#">ссылкой</a> в никуда.</p>
|
|
|
```
|
|
|
|
|
|
Всегда можно нажать *Ctrl+U* и увидеть исходный код страницы (разметку).
|
|
|
|
|
|
SVG работает так же, только разметка описывает не текст с оформлением,
|
|
|
а изображение. HTML указывает, как отображать определенные блоки текста,
|
|
|
а SVG указывает, какие геометрические фигуры нарисовать.
|
|
|
|
|
|
Если щелкнуть правой кнопкой мыши по изображению выше
|
|
|
и выбрать «Показать изображение», оно откроется отдельно.
|
|
|
Тем же *Ctrl+U* можно посмотреть код разметки гистограммы,
|
|
|
который в итоге будет выводить программа.
|
|
|
|
|
|
Если если программа будет печатать размету SVG вместо звездочек,
|
|
|
можно перенаправить вывод программы в файл с расширением `*.svg`
|
|
|
и открыть его браузером — будет нарисовано изображение.
|
|
|
В случае ошибки оно нарисовано не будет.
|
|
|
В этом случае можно *Ctrl+U* посмотреть, что программа вывела не так.
|
|
|
Как правило, в окне кода, которое открывается по *Ctrl+U,*
|
|
|
красным цветом выделяются синтаксические ошибки, то есть некорректная разметка.
|
|
|
|
|
|
## Формат SVG
|
|
|
|
|
|
Разберем заголовок SVG:
|
|
|
|
|
|
```xml
|
|
|
<?xml version='1.0' encoding='UTF-8' ?>
|
|
|
<svg width='300' height='100' viewBox='0 0 300 100' xmlns='http://www.w3.org/2000/svg'>
|
|
|
```
|
|
|
|
|
|
Так описывается изображение размером 300×100 точек.
|
|
|
|
|
|
Система координат SVG отличается от математической:
|
|
|
ось Y направлена вниз, то есть точка *(0,0)* находится в верхнем левом углу.
|
|
|
Координаты в SVG действительные, то есть может быть точка *(0.5, 3.14).*
|
|
|
|
|
|
Фрагменты вида `<svg>`, `<text>` или `<rect>` называются *элементами.*
|
|
|
Параметры вида `height='300'` называются *атрибутами* элементов.
|
|
|
Элементы имеют открывающий тэг (`<svg>`), закрывающий тэг (`</svg>`)
|
|
|
с косой чертой и содержимое между ними (`<text>содержимое</text>`).
|
|
|
Если содержимого нет, то тэг может быть сразу закрыт: `<rect ... />`.
|
|
|
|
|
|
Подробнее об SVG можно прочитать
|
|
|
[в спецификации](https://developer.mozilla.org/en-US/docs/Web/SVG/Element),
|
|
|
но все нужные для ЛР сведения даны в этих указаниях.
|
|
|
|
|
|
SVG поддерживает множество элементов, среди них:
|
|
|
|
|
|
* `<text x='20' y='35'>anything you want</text>`:
|
|
|
текст «anything you want», левый нижний угол которого в точке *(20,35);*
|
|
|
* `<rect x='0' y='0' width='100' height='200' />`:
|
|
|
прямоугольник 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 << "<?xml version='1.0' encoding='UTF-8'?>\n";
|
|
|
cout << "<svg ";
|
|
|
cout << "width='" << width << "' ";
|
|
|
cout << "height='" << height << "' ";
|
|
|
cout << "viewBox='0 0 " << width << " " << height << "' ";
|
|
|
cout << "xmlns='http://www.w3.org/2000/svg'>\n";
|
|
|
}
|
|
|
|
|
|
void
|
|
|
svg_end() {
|
|
|
cout << "</svg>\n";
|
|
|
}
|
|
|
```
|
|
|
|
|
|
Обратите внимание на пробелы в строках, например, перед закрывающей кавычкой
|
|
|
во фрагменте `"viewBox='0 0 "`.
|
|
|
Они необходимы между атрибутами.
|
|
|
Также обратите внимание на использование одинарных кавычек.
|
|
|
Двойные кавычки ограничивают в C++ строковые литералы;
|
|
|
одинарные кавычки внутри них выводятся «как есть» в результирующую строку.
|
|
|
Путаница кавычек или отсутствие некоторых пробелов сделает SVG некорректным.
|
|
|
|
|
|
Пусть начальная реализация графического вывода гистограммы всегда выводит
|
|
|
пустое изображение фиксированного размера:
|
|
|
|
|
|
``` cpp
|
|
|
void
|
|
|
show_histogram_svg(const vector<size_t>& 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.txt >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
|
|
|
<text x='20' y='35'>anything you want</text>
|
|
|
```
|
|
|
|
|
|
На C++ точно такую строку можно вывести следующим образом (`svg_text()`)
|
|
|
должна размещаться выше `show_histogram_svg()`:
|
|
|
|
|
|
``` cpp
|
|
|
void
|
|
|
svg_text(double left, double baseline, string text) {
|
|
|
cout << "<text x='20' y='35'>anything you want</text>";
|
|
|
}
|
|
|
```
|
|
|
|
|
|
Чтобы вместо координаты `x` (числа 20) выводить значение `left`:
|
|
|
|
|
|
``` cpp
|
|
|
cout << "<text x='" << left << "' y='35'>anything you want</text>";
|
|
|
```
|
|
|
|
|
|
Обратите внимание на то, что сохранены одинарные кавычки внутри строк C++
|
|
|
(в двойных кавычках) — они необходимы по правилам SVG.
|
|
|
|
|
|
**Самостоятельно.**
|
|
|
Закончите реализацию `svg_text()`,
|
|
|
чтобы подставлять значение координаты `baseline` и текст надписи `text`.
|
|
|
|
|
|
Для проверки выведем высоту первого столбца гистограммы:
|
|
|
|
|
|
``` cpp
|
|
|
void
|
|
|
show_histogram_svg(const vector<size_t>& 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.txt >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
|
|
|
<rect x='50' y='0' width='30' height='30' stroke='red' fill='#ffeeee'/>
|
|
|
```
|
|
|
|
|
|
[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|`<font color="#999">`▮`</font> — цвет `#999` \
|
|
|
`5|`<font color="#111">`▮▮▮▮▮`</font> — цвет `#111` \
|
|
|
`3|`<font color="#555">`▮▮▮`</font> — цвет `#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
|
|
|
|
|
|
Добавьте рамку вокруг гистограммы, используя пунктирные линии. Для отрисовки
|
|
|
пунктирной линии можно использовать стандартный элемент `<line>`, установив
|
|
|
в нем атрибут `stroke-dasharray = '10 10'`
|
|
|
|
|
|
#### Вариант 12
|
|
|
|
|
|
Добавьте на ось подписей границы столбцов по аналогии с заданием этого
|
|
|
варианта в лабораторной работе 1.
|
|
|
|
|
|
#### Вариант 13
|
|
|
|
|
|
Отображайте гистограмму вертикально с подписями снизу.
|
|
|
|
|
|
```
|
|
|
*
|
|
|
*
|
|
|
* *
|
|
|
* *
|
|
|
* * *
|
|
|
_ _ _
|
|
|
3 5 1
|
|
|
```
|
|
|
|
|
|
Предусмотреть расчет `IMAGE_HEIGHT` таким образом, чтобы вся гистограмма
|
|
|
вмещалась в область рисунка.
|
|
|
|
|
|
#### Вариант 14
|
|
|
|
|
|
Разделяйте каждый столбец пунктирными линиями длиной `IMAGE_WIDTH`.
|
|
|
Запрашивайте у пользователя шаблон пунктира. Шаблон пунктира задается
|
|
|
в атрибуте `stroke-dasharray` блока `<line>` в виде `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|`<span style="opacity: 0.2">`▮`</span> — прозрачность 0.2 \
|
|
|
`5|`<span style="opacity: 1.0">`▮▮▮▮▮`</span> — прозрачность 1.0 \
|
|
|
`3|`<span style="opacity: 0.6">`▮▮▮`</span> — прозрачность 0.6
|
|
|
|
|
|
#### Вариант 18
|
|
|
|
|
|
Позволять пользователю делать оформление текста -
|
|
|
<span style="text-decoration: underline">подчеркивание</span>,
|
|
|
<span style="text-decoration: overline">надчеркивание</span>, ~~зачеркивание~~
|
|
|
текста. За оформление шрифта отвечает атрибут `text-decoration`. Сделать
|
|
|
`'none'` значением по умолчанию. Допустимые значения: `none`, `underline`,
|
|
|
`overline`, `line-through`. Проверять введенное пользователем значение, и если
|
|
|
оно не одно из допустимых, запрашивать значение заново, выдавая предупреждение.
|