Задание по созданию эл. магазина

Первые шаги

Ссылка на магазин

Данная статья посвящена созданию интернет-магазина на системе WordPress. WordPress – это веб-приложение, позволяющее владельцам, редакторам управлять своими сайтами и публиковать контент без какого-либо знания программирования. Благодаря данной cms системе люди способны создавать свои личные блоги, интернет-магазины, новостной сайт или же портфолио человека и так далее. Поэтому, она лежит в основе 35% всех сайтов в интернете и их число растет с каждым днем.

Но прежде чем начать, необходимо установить и настроить WordPress. Работать с ним можно как на общем хостинге, так и на локальном, используя, например, программу “XAMPP”. И на данный момент существует множество статей или же видеоуроков, показывающие поэтапные шаги установки и запуска данной системы. Поэтому, этот момент я пропущу и покажу лишь стартовую страницу, которую вы должны получить при правильной установки.

После этого, мы готовы к работе. Первым делом, я решил сменить тему сайта, которая максимально бы подходила под его тематику, а именно: интернет-магазин. Что это такое? Данная cms система имеет встроенные темы, которые отвечают за внешний вид сайта и сразу после ее установки все файлы устанавливаются на хостинг. То есть, ее основная задача заключается в выводе содержимого ресурса в удобном для пользователя виде. Чтобы реализовать это, мы на нашей стартовой странице наводим на название нашего сайта и выбираем вкладку “Темы”, после чего у нас открывается новое окно, где нажимаем на кнопку “Добавить” и выбираем понравившуюся тему. Выбрав тему, устанавливаем ее и после активируем. В моем случае, была выбрана тема “HoneyPress”.

Следом, необходимо удалить все автоматический созданные записи, чтобы получилась чистая страница. Для этого, в консоли, переходим во вкладку “Записи” → “Все записи”, где выбираем все и удаляем. Теперь у нас абсолютно чистая страница, с которой можно работать.

Далее, я решил оформить главную страницу. Для этого, я перехожу в консоль → страницы и добавляю новую страницу. Я назвал ее точно также, как и магазин – “МойДиван”. После, необходимо ее сделать главной. Для этого, переходим во вкладку “Настройки” → “Чтение” и выбираем в пункте “На главной странице отображать” пункт “Статическую страницу”, выбирая при этом главную страницу, ранее созданную “МойДиван”.

Создав главную страницу, я приступил к ее оформлению, а именно: заполнение информацией о нашем магазине. Для этого я использовал плагин “Elementor”. Elementor – это плагин, который является самым популярным конструктором страниц и позволяющий не только добавлять различного рода информацию, но и создавать оригинальный дизайн сайтов. Для его установки необходимо перейти на вкладку “Плагины” → “Добавить новый” и в поисковой строке прописать его название, после чего установить и активировать его. Проделав это, на главной странице сверху появится вкладка “Редактировать в Elementor”.

Как видно из рисунка, данный плагин представляет собой совокупность блоков, отвечающих за определенные функции, и чтобы взаимодействовать с ними, необходимо выбрать нужный и перетащить его на страницу. Например, если я хочу заполнить страницу текстом, то я выбираю блок “Текстовый редактор”, перетаскиваю его вправо на страницу и пишу то, что необходимо. Разобравшись с принципом работы плагина, я заполнил страницу нужной информацией.

Помимо этого, на главной странице была размещена кнопка “Свяжись с нами!”, нажав которую, открывалась всплывающее окно с контактной информацией, необходимой для того, чтобы в случае возникновения вопросов или же консультации, клиент смог связаться с нами и получить всю необходимую информацию. Реализовано это было используя плагин “Popup Maker”. Устанавливается он аналогично, как и плагин “Elementor”. Данный плагин является как раз самым популярным по созданию всплывающих окон с различной информацией, такой как контактная форма, обновление контента и так далее.

Установив Popup Maker он появится у нас в консоли, после чего переходим в него и создаем новое всплывающее окно. В моем случае я назвал его “Связь” и в качестве заголовка внутри всплывающего окна прописал “Наши контактные данные”, а внутри поля прописал необходимую информацию для отображения. Результат представлен внизу.

Кроме того, после создания, у созданного Popup имеется определенный CSS класс, прописанный в окне “Все всплывающие окна”. В моем случае это popmake-230. Данный элемент очень важен, потому что далее мы переходим в Elementor и создаем кнопку, где ее не только оформляем внешне, но и в расширенных настройках, в поле “CSS классы” указываем popmake-230. На этом, наше окно создано и полностью работает, что показано на рисунке ниже.

На этом, оформление главной страницы завершено, итоговый результат представлен ниже.

После написания информацией он нас, я приступил к заполнению магазина товарами.

Работа с плагином WooCommerce

WooCommerce – это бесплатный плагин для электронной коммерции, который разработан специально для WordPress. Он имеет целый ряд функций, например: различные способы доставки, приема платежей, настраиваемые типы товаров и так далее. Поэтому, он отлично подходит для наших целей. Устанавливается он аналогично предыдущим с появлением одноименной вкладки в консоли. После установки, его необходимо настроить. Для этого переходим в его настройки, во вкладку основные, где указываем нужную информацию. Результат на рисунке ниже.

Но, как можно заметить, у нас в магазине нет абсолютно никакого меню. Для этого, мы переходим во вкладку “Товары” в консоли и создаем необходимые категории. В моем случае это мебель для гостиной, мягкая мебель и офисная мебель. Далее, создаем меню во вкладке “Внешний вид” → “Меню”. Название я дал, как “Верхнее меню”, куда перетаскиваю следующие параметры, изображенные на рисунке.

Как можно увидеть, в него я перетащил созданные ранее категории и параметр “FiboSearch bar”, который является поисковой строкой товаров на сайте. FiboSearch bar – это Ajax плагин, поэтому, если вы хотите добавить в меню поиск товаров, то необходимо сначала его установить. Также, важным моментом создания является тот факт, что в пункте “Настройки меню” важно поставить область, где будет находится наше меню. В моем случае это область отображения – “основной” Теперь, меню должно появится на сайте. Но есть и второй способ его создания. Для этого, на главной странице, переходим в раздел “Настроить” → “Меню” и проделываем все те же операции. Помимо этого, в настройках, я также настроил еще и иконку нашего магазина, и отображение корзины не в виде категории, а в виде изображения. Итоговый результат реализации меню представлен ниже.

Следом, необходимо загрузить товары в магазин. Для этого, в консоли я перехожу во вкладку “Товары” → “Добавить”. Где, в открывшемся окне, я заполняю всю информацию о товаре: название, описание, изображение, стоимость, состояние о наличие и так далее. Самое важное – это указать категорию товара, чтобы он отображался в нужном нам разделе. Список ранее созданных нами категорий изображен справа, где мы просто напротив необходимой ставим галочку. Пример добавления товара изображен ниже.

Точно таким же образом добавляем необходимо число товаров для каждой категории. На рисунке следом изображен результат заполнения магазина товарами по категории “Офисная мебель”, но товары добавлены абсолютно в каждый раздел.

Как видно из рисунка выше, у нас еще присутствует сортировка товара, которая добавилась автоматический при добавлении товара, ведь это встроенный функционал плагина WooCommerce.
Пока что мы показали, как выглядят товары на общей страницы определенной категории, но как товар отображается на своей собственной странице? Пример отображения находится ниже.

Далее, необходимо реализовать корзину, а именно указать границы доставки, их стоимость и способы. Для этого опять переходим в консоли во вкладку “WooCommerce” → “Настройки” → “Доставка”, где мы создаем необходимые зоны. В моем случае – это Челябинск и Челябинская область. В первой зоне я указываю через троеточие два индекса города Челябинска (его самый первый и последний), а во второй зоне два индекса области. После я указываю методы доставки, указывая их название, которое будет видеть покупатель и указываю их стоимость. Пример изображен снизу.

Для чего все это было сделано? Когда покупатель будет оформлять заказ, он укажет свой индекс доставки. Исходя из этого индекса система будет понимать, находится ли клиент в городе Челябинске или же в области и будет предлагать ему способы доставки со стоимостью, которые мы указывали для каждой зоны, рассчитывая конечную цену.

Далее, переходим из вкладки “Доставка” в “Платежи”, где выбираем нужные нам способы оплаты и настраиваем их нажав на кнопку “Управление”.

Проделав все вышесказанное, наша корзина и страница оформления готова к работе. Поэтому, добавляем товар в корзину и смотрим результат.

Нажав на кнопку “Оформить заказ” мы переходим на страницу оформления, изображенная внизу.

Нажав на кнопку подтверждения заказа появляется следующее окно:

Важным моментом является приходит ли заявка администраторам сайта при заказе? Чтобы проверить это, необходимо перейти в консоль → вкладка “WooCommerce” → “Заказы”. Увиденный результат изображен снизу.

Как мы видим, заказ приходит и все исправно функционирует.

ВЫВОД

Исходя из всего выше проделанного можно сделать вывод о том, что в современном мире для создания сайта, блога или же интернет-магазина не обязательно обладать знаниями программиста. Ведь современный мир постоянно развивается и придумываются различные интернет-технологии, упрощающие жизнь человека. Одной из такой как раз является WordPress, которая, как выяснилось, не зря является самой популярной системой по созданию и управлению сайтами.

Это обосновывается тем, что система имеет не только удобный и простой интерфейс для создания веб-ресурсов, но она и не требует использования или же написания кода, хотя пользователь имеет такую возможность. Однако, самое главное ее достоинство – это наличие плагинов, которые расширяют функционал ресурса, что и было доказано в этой работе. Ведь, благодаря плагину WooCommerce, мы смогли простыми действиями создать полноценный интернет-магазин, обладающий всеми базовыми функциями не написав при этом ни одной строчки кода. А плагин Elementor позволил оформить главную страницу магазина, содержащую основную информацию. Поэтому, используя данную cms систему и ее плагины, абсолютно каждый сможет создать необходимый веб-ресурс.

Ссылка на магазин

Вопросы к беседе с Алексеем Анкудиновым

Перед встречей, был подготовлен ряд вопросов:
1) Какие плюсы и минусы можно выделить, что у Вас не функциональная структура предприятия?
2) Как Вы обеспечиваете приток разработчиков в компанию?

Конспект лекции "Итоговая программа"

Некоторые консультанты убеждены в том, что для того, чтобы провести какие-то маркетинговые исследования, нужно вынимать стороннюю компанию и тратить достаточно значительные бюджеты. Но тут нельзя так однозначно ответить. С одной стороны - маркетолог внутри компании может и способен провести часть этих исследований, а с другой стороны - если в компании этого не делается, то это означает лишь то, что маркетолог либо не хочет, либо не умеет пользоваться общепризнанными технологиями. И для анализа внешней среды главное понять следующие моменты:
1) Уровень конкурентного окружения. И для того, чтобы его понять, существует два метода. Первый - это интернет-поиск, который предполагает просмотр интернет-ресурсов конкурентов, а именно: как они себя позиционируют, какие продукты предлагают, каковы ценовые предложения на сайте компании и так далее. Помимо этого, необходимо просмотреть ключевые поисковые запросы в «Яндексе» и «Google», по которым вы предполагаете, что ваши клиенты вас ищут, а также просмотр по рекламным позициям кто из конкурентов какую рекламу даёт. У кого-то это будут лэндинговые страницы, посадочные страницы с уникальными конкурентными предложениями, у кого-то будет просто имиджевая реклама. Второй метод – это прозвон ваших конкурентов, их отдела продаж, где задаёте те вопросы, на которые вы хотите получить ответы. В данном случае исследование получается более качественное, когда интервьюер прикидывается полным профнепригодным человеком в данной отрасли и задаёт те вопросы которые мог бы сформулировать именно реальный клиент.
2) Портрет целевой аудитории компании. Внутри целевой аудитории находятся несколько разных сегментов, которые, бывает, не учитываются совсем. И сегментирование рынка производится исключительно по психо-демографическим параметрам: возраст, пол, место проживания, уровень дохода и так далее. Но на сегодняшний момент эти факторы являются вторичными, ведь сегмент – это часть потребительского рынка, которая к вашему продукту предъявляет идентичные требования, являющимися ценностями целевой аудитории. Из всего этого возникает вопрос, как же просегментировать аудиторию? На первом этапе, необходимо проработать гипотезы, которые мы предполагаем, что являются ценностями со стороны наших сегментов. Этих потребительских ценностей может быть достаточно большое количество. И, к сожалению, если компания этим не занимается и не уделяет достаточно внимания разработке продукта именно на языке ценностей потребительской аудитории, то она скатывается в единственную потребительскую ценность – это цена. И там начинает толкаться со всеми другими конкурентами, которые предлагают аналогичный, ничем не отличающийся товар. И здесь выигрывает тот, кто даст более низкую цену. Многие ошибочно считают, что сегодня рынку ничего кроме цены неинтересно. Это утверждение означает только то, что вы не просегментировали, не посмотрели на все потребительские ценности, которые есть на рынке. Так какие они могут быть? Например, ценность экономии времени, под которую затачиваются все интернет-магазины, предоставляя возможность не ходить по магазинам, а сделать заказ через интернет.
3) Продукт. Данный вопрос, к сожалению, проработан на сегодняшний момент у достаточно небольшого числа компаний. Разработка продукта – это реализация ценностей, которые вы выявили со стороны своей аудитории, в ваш продукт. Это будет либо модификация продукта, либо его усовершенствование, либо это будут какие-то дополнительные сервисные предложения.
4) Управления активной клиентской базой. Значительная доля компании работают со своей клиентской базой практически вслепую: они не понимают, кто потенциальная аудитория, потому что не проанализировали потребительские ценности; не совсем понимают с работают, от чего теряют клиентов; почему клиенты реже покупают или недостаточно высокий чек и так далее. Так вот для анализа активной клиентской базы есть достаточно распространенный метод ABC-анализа, суть которого достаточно хорошо описана в интернете.
5) Возможно также применить и очень эффективную методику, а именно: SWOT-анализ. SWOT-анализ – это анализ сильных и слабых сторон внутри компании и анализ возможностей и угроз со стороны. Чтобы он имел практическое применение, необходимо всегда анализировать проблемные поля бизнеса. И здесь вы получите огромное количество инсайтов и готовых решений, что вам необходимо сделать в случае наступления внешнего фактора, то есть возможности или угрозы.

Визуализация пандемии коронавируса с помощью фоновых картограмм

Визуализация данных это наиболее эффективный метод иллюстрации и объяснения сложной информации, особенно числовых данных, в простой и удобоваримой форме. Кроме того, при грамотном исполнении, инфографика при интерпретации данных позволяет уменьшить или смягчить систематические ошибки. Один из заслуживающих особого внимания видов визуализации — анимированные фоновые картограммы или хороплеты. И в данной статье мы, используя эти виды, визуализируем пандемию коронавируса, что на данный момент очень актуально.

Но прежде чем начать, давайте разберем определения. Фоновая картограмма или хороплет — это тип тематической карты, на которой области или регионы заштрихованы пропорционально и в соответствии со значением заданного измерения данных.

Статические хороплеты наиболее полезны, когда надо сравнить какие то показатели по регионам. Например, сравнить уровень преступности в данный момент в каждом субъекте Российской Федерации, что можно визуализировать с помощью статической фоновой картограммы.

Анимированная или динамическая фоновая картограмма похожа на статическую, за исключением того, что добавляется время. Это третье измерение делает визуализацию исключительно интересной и мощной.

Для визуализации пандемии коронавируса, я использовал набор данных Novel Corona Virus 2019 от Kaggle, который можно найти здесь. Набор данных получен из нескольких источников, включая Всемирную организацию здравоохранения, Национальную комиссию здравоохранения Китайской Народной Республики и Центры США по контролю за заболеваниями.

Статический хороплет

Начнем мы с создания статического хороплета. Работать я буду в привычном мне PyCharm. Первым делом, я открываю редактор и создаю проект. После создания, я добавляю в него папку, под названием “data”, где у меня будет хранится файл csv формата, скаченный с Kaggle и содержащий данные о пандемии по всему миру.

После этого, я, через терминал, устанавливаю необходимые библиотеки. Команды для установки представлены ниже:

pip install numpy 
pip install pandas
pip install plotly

Проделав операции выше, создаем python файл, и мы готовы к визуализации. Код для этой инфографики выглядит следующим образом:

#Статический хороплет


# Импорт библиотек
import numpy as np
import pandas as pd
import plotly as py
import plotly.express as px
import plotly.graph_objs as go
from plotly.subplots import make_subplots
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot

# Чтение данных
df = pd.read_csv("data/covid_19_data.csv")

# Переименуем колонки
df = df.rename(columns={'Country/Region':'Страна'})
df = df.rename(columns={'ObservationDate':'Дата'})
df = df.rename(columns={'Confirmed':'Подтверждено'})

# Манипуляции с оригиналом Dataframe
df_countries = df.groupby(['Страна', 'Дата']).sum().reset_index().sort_values('Дата', ascending=False)
df_countries = df_countries.drop_duplicates(subset = ['Страна'])
df_countries = df_countries[df_countries['Подтверждено']>0]

# Создание фоновой картограммы
fig = go.Figure(data=go.Choropleth(
    locations = df_countries['Страна'],
    locationmode = 'country names',
    z = df_countries['Подтверждено'],
    colorscale = 'Reds',
    marker_line_color = 'black',
    marker_line_width = 0.5,
))
fig.update_layout(
    title_text = 'Подтверждённые заболевания 28 марта 2020',
    title_x = 0.5,
    geo=dict(
        showframe = False,
        showcoastlines = False,
        projection_type = 'equirectangular'
    )
)

fig.write_html('Static_choroplete.html', auto_open=True)

Здесь, мы импортируем установленные ранее библиотеки, после читаем csv файл из которого будем брать данные, указывая при этом его местонахождение и название. Далее, переименовываем колонки для удобного восприятия и проводим некоторые манипуляции с оригиналом Dataframe: группировки и возвращения ряда с удаленными повторяющимися значениями. После создаем фоновую картограмму и последней командой мы указываем на создание и запуск html файла, содержащий наши данные. Результат представлен ниже:

На рисунке, представлена статическая диаграмма общего количества подтвержденных случаев коронавируса по странам по состоянию на 28 марта 2020 года. Вы можете видеть, что страны с наибольшим количеством подтверждённых случаев заболевания — это США, Китай и Италия, а также несколько других европейских стран.

Анимированная фоновая картограмма

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

#Анимированная фоновая картограмма

# Импорт библиотек
import numpy as np
import pandas as pd
import plotly as py
import plotly.express as px
import plotly.graph_objs as go
from plotly.subplots import make_subplots
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot

# Чтение данных
df = pd.read_csv("data/covid_19_data.csv")

# Переименуем колонки
df = df.rename(columns={'Country/Region': 'Страна'})
df = df.rename(columns={'ObservationDate': 'Дата'})
df = df.rename(columns={'Confirmed': 'Подтверждено'})

# Манипуляции с оригиналом Dataframe
df_countrydate = df[df['Подтверждено'] > 0]
df_countrydate = df_countrydate.groupby(['Дата', 'Страна']).sum().reset_index()
df_countrydate

# Создание фоновой картограммы
fig = px.choropleth(df_countrydate,
                    locations="Страна",
                    locationmode="country names",
                    color="Подтверждено",
                    hover_name="Страна",
                    animation_frame="Дата"
                    )
fig.update_layout(
    title_text='Глобальное распространение короновируса',
    title_x=0.5,
    geo=dict(
        showframe=False,
        showcoastlines=False,
    ))

fig.write_html('Animated_background_cartogram.html', auto_open=True)

В данном коде, мы проделываем те же операции, что и при статическом хороплете, но с небольшими отличиями. Результат представлен ниже:

На рисунке, мы видим общее количество подтвержденных случаев коронавируса по странам во времени. И вы можете видеть, что до недавнего времени больше всего заболевших было в Китае.

Конспект лекции "Андрей Себрант - место человека в мире технологий"

Должен ли инженер думать о возможном вреде технологии при её создании?

Нужно быть одержимым идеей, чтобы добиться успеха в том, что ты делаешь. Задумываться над этикой не нужно, отвлекаться на это – помеха конечному результату.

Должны ли этик и инженер работать в паре?

Этику и инженеру не нужно работать в паре. Это не позволит достичь конечного результата, этик будет «мешать» этому.

О машинном обучении.

До появления машинного обучения единственным способом обучить машину – описать пошагово, что необходимо делать. Сейчас, это происходит за счет обучения машины действиями, которые мы сами до конца не понимаем, мы делаем это на подсознании.

В чем машины заменят людей?

Например, сейчас уже существуют машины, которые заменяют дворника и убирают мусор. Но гораздо сложнее заменить сантехника, ведь объяснить машине, как в миллионах домов состоит коммуникация труб, узлов, объяснить, как это чинить и ползать там, то проще иметь человека. Это уже более сложная и дорогостоящая задача, чем заменить дворника.

Можем ли мы прогнозировать будущее с помощью больших данных?

Нет, люди не могут прогнозировать будущее с помощью больших данных, потому что никакие большие данные не позволяют предсказать прорыв человечества в той или иной сфере. И через 20 лет могут возникнуть такие технологии, про которые никто из нас сейчас не догадывается, поэтому спрогнозировать будущее не получится.

Какое будущее ждет Россию?

Исходя из истории, тренд, где жертвой становится какой-либо класс, повторится еще раз. Сейчас, офисные работы среднего класса населения очень легко автоматизировать и удар будет происходить именно по ним. Поэтому и приводился пример с сантехником, ведь, казалось бы, его заменить очень сложно, а бухгалтера легко. Вот этот тренд очень сильно, скорее всего, и затронет нашу страну.

Про аналитику персональных данных.

Анализировать персональные данные человека не желательно. Когда ты начинаешь ему “подсовывать” что-то, что ему не интересно или же точку зрения, с которой он не согласен, то ему это не нравится, и он уходит на другой сервис.

Есть ли персональные данные, которые нельзя анализировать?

Большинство компаний, которые живут по рекламной модели, стараются не анализировать данные о состоянии здоровья человека. Ведь человек, когда смотрит симптомы болезни или читает информацию о ней, при этом не желая рассказывать об этом своим родственникам, не хочет, чтобы ему в дальнейшем предлагали к просмотру информацию об этом. Например, когда он со своими родственниками или знакомыми захочет что-нибудь посмотреть в интернете и будет идти сплошная реклама средств лечения от этой болезни, то будут возникать вопросы, которые пользователь хочет избежать. Как итог, фармакологическая компания ничего не заработала, а пользователь ушел на сторонний сервис.

Есть ли персональные данные, которые Яндекс осознанно не собирает?

Есть. К ним относится история по здоровью, личная переписка и так далее. Почему нельзя анализировать личную переписку? Все очень просто. Например, если у человека ушел из жизни близкий человек, и он делится этим в переписке, то алгоритм будет предлагать ему ритуальные услуги. Это очень сильно обидит человека, плюс породит огромный скандал, который повлияет на репутацию компании. Именно поэтому, никто не анализирует личную переписку.

Как Яндекс обрабатывает персональные данные?

Обработка персональных данных в Яндексе производится используя этические нормы.

Конспект лекции "Укрепление доверия россиян к искусственному интеллекту и развитие отечественной отрасли ИИ"

Искусственный интеллект – система или набор алгоритмов, воспроизводящий определённые когнитивные способности людей, благодаря которым человек ставит своей целью принятие решений или помощь в принятии решений. И с этой точки зрения можно сказать, что технологии искусственного интеллекта в наше время абсолютно везде. Например, во всех гаджетах, интерфейсах и поверхностях, с которыми люди взаимодействуют. Ведь любой наш гаджет, приложение, которое мы открываем, практически всё, что мы говорим или слышим, обрабатывается через системы распознавания речи, системы компьютерного зрения. Это используются для того, чтобы человек, например, который включает приложение, действительно был владелец телефона и так далее.

Основная задача заключается в том, чтобы объёмами цифр занималась машина, а не огромное количество людей в штате, ведь человек может совершать ошибки.

Кроме того, там, где не опасно, человек делегирует решения машине, а там, где существует вопрос какой-то этической дилеммы – человек есть и будет финальной точкой принятия решения. В этом смысле технологии искусственного интеллекта помогают людям. Например, современная авиатехника имеет широкие возможности автопилотирования, но в какие-то момент подключается живой пилот. Поэтому, благодаря искусственному интеллекту мы освобождаем человека для того, чтобы делать более сознательные или творческие задачи, что порождает в том числе новые направления и навыки.

Но что делать если человек теряет свои когнитивные функции? Ведь если он их не тренирует, понятно, что они снижаются. (для чего детям уметь считать в столбик, если они могут открыть калькулятор на телефоне, компьютере и посчитать там) Над этим вопросом сейчас думают многие – и разработчики, и те, кто использует технологические системы искусственного интеллекта, которые есть. Но тут стоит вопрос выбора людей - отказываясь от участия в каких-то мыслительных или рутинных действиях, мы проходим через так называемый «рескиллинг». Но проходя рескиллинг, мы начинаем осваивать новое.

Более ли консервативен и осторожен искусственный интеллект чем человек? Все это зависит от того, как его обучают. Если мы задаём ему цель быть максимально консервативным и минимизировать негативные последствия – значит, он будет работать консервативно. В конечном счёте, за любым алгоритмом стоит разработчик. Это очень важный момент, за которым важно закрепить ответственность – так у людей будет понимание, что где-то должен быть стоп-кран, или возможность корректировки решения, предлагаемого искусственным интеллектом, если что-то идёт не так.

По сценарию вышедшего из-под контроля искусственного интеллекта, который не только сам принимает решения, а уже видя, что человек для него представляет опасность, начинает избавляться от человека. Есть ли крайняя кнопка или возможность у человека отключить всю систему? В целом, наверное, надо подумать над тем, чтобы у нас была возможность обесточить систему или просто деактивировать какое-то решение, принимаемое там, где мы отвлеклись. Но пока все эти страхи вызваны больше результатом научной фантастики и фильмов про будущее…

Есть ли угроза искусственного интеллекта для существующего типа рынка труда? Это естественный цикл осознания принятия решений, когда нам говорят, что вас скорее всего заменят или роботизируют. Это то, с чего все начинают общение с теми, кто приходит им разрабатывать что-то полезное для повышения точности принятия решений и так далее. Но очень быстро становится понятно, когда человек погружается в задачу, что на самом деле это пришли не его автоматизировать, а пришли избавить от рутины, либо существенно повысить производительность тех действий, которые всё ещё требуются от человека, для того, чтобы сделать какой-то сервис, реализовать какое-то решение.

Какой из страхов искусственного интеллекта наиболее реальный? Самая большая опасность – плохая интерпретируемость моделей и их нестабильность. Если модели начинают принимать те решения, которые мы не ожидали. Здесь действительно важно иметь те самые ограничители и системы мониторинга качества моделей.

Использование фреймворка Flask для реализации web-приложений

В данной статье я создам свое первое веб-приложения на Flask, а также попрактикуюсь в использовании шаблонов.

Но прежде чем начать, давайте разберемся что такое вообще Flask. Flask– это легкая веб платформа Python, которая предоставляет полезные инструменты и функции для создания веб приложений на языке Python. Это дает разработчикам гибкость и является доступной средой для новых разработчиков, поскольку вы можете быстро создать веб приложение, используя только один файл Python.

Создание и запуск своего первого веб-приложения

Работать я буду в PyCharm. Поэтому, первым делом я создаю свой проект и далее устанавливаю Flask через встроенный терминал с помощью команды pip install flask. Далее, я создал файл app1.py, где будет написано простое веб-приложение, отображающее HTML-код в браузере.
Открыв файл, я написал следующий код:

from flask import Flask

app = Flask(__name__)


@app.route('/')
def hello():
    return '

Hello, World!

'

В нем мы сначала импортируем объект Flask из пакета flask. Затем используем его для создания экземпляра приложения Flask, присвоив ему имя app. Мы передаем специальную переменную __name__, которая содержит имя текущего модуля Python. Это имя сообщает экземпляру, где он находится; это нужно, потому что Flask устанавливает некоторые пути за кулисами.

Создав экземпляр приложения, мы можем использовать его для обработки входящих веб запросов и отправки ответов пользователю. @app.route — это декоратор, который превращает обычную функцию Python в функцию просмотра Flask, который преобразует возвращаемое значение функции в HTTP-ответ, отображаемый HTTP-клиентом, например, веб браузером. Мы передаем значение ‘/‘ в @app.route (), чтобы указать, что эта функция будет отвечать на веб запросы для URL-адреса /, который является основным URL-адресом.

Функция просмотра hello() возвращает строку <!--‘Hello, World!‘--> в качестве ответа HTTP.

Теперь у нас есть простое приложение Flask в файле Python с именем app1.py.

Далее, необходимо его запустить чтобы увидеть результат функции просмотра hello(), отображаемой в веб браузере. Как это сделать? Во встроенном терминале я прописываю команду set FLASK_APP=app1, благодаря которой я сообщаю Flask, где найти приложение app1. После, прописываю set FLASK_ENV=development, которая говорит о том, что я хочу запустить приложение в режиме разработки (чтобы можно было использовать отладчик для обнаружения ошибок). Наконец, я прописываю flask run. После запуска, вы увидите следующее:

* Serving Flask app 'app1' (lazy loading)
 * Environment: development
 * Debug mode: on
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 188-110-549
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Эти выходные данные содержат несколько частей информации:
•Название приложения, которое вы запускаете («app1»).
•Среда, в которой выполняется приложение (development).
•Debug mode (Режим отладки): on означает, что отладчик Flask запущен. Это полезно при разработке, поскольку предоставляет подробные сообщения об ошибках, когда что-то идет не так, что упрощает поиск и устранение неисправностей.
•Приложение работает локально по URL-адресу http://127.0.0.1:5000/. 127.0.0.1 — это IP-адрес, который представляет локальный хост вашего компьютера, а: 5000 — это номер порта.
Далее, открываем браузер и введем URL-адрес http://127.0.0.1:5000/. Вы увидите текст “Hello, World!” в качестве ответа. Это подтверждает, что ваше приложение успешно работает.

Кроме этого, можно добавить несколько маршрутов для отображения разных страниц в зависимости от запрошенного URL.

Маршрут — это URL-адрес, который вы можете использовать, чтобы определить, что пользователь получает, когда посещает ваше веб приложение в своем браузере. Например, http://127.0.0.1:5000/ — это основной маршрут, который может использоваться для отображения главной страницы. URL-адрес http://127.0.0.1:5000/about может быть другим маршрутом, используемым для страницы «About», которая дает посетителю некоторую информацию о вашем веб приложении.

Для этого, в app1.py прописываем следующее:

from flask import Flask

app = Flask(__name__)


@app.route('/')
def hello():
    return '

Hello, World!

' @app.route('/about/') def about(): return '

This is a Flask web application.

'

Здесь, мы добавили новую функцию about(). Эта функция украшена декоратором @app.route(), который преобразует ее в функцию представления, которая обрабатывает запросы к конечной точке http://127.0.0.1:5000/about. Для просмотра результата переходим по этому URL-адресу.

Но можно использовать и несколько маршрутов для одной функции просмотра. Для этого, изменяем app1.py следующим образом:

from flask import Flask

app = Flask(__name__)

@app.route('/')
@app.route('/index/')
def hello():
    return '

Hello, World!

' @app.route('/about/') def about(): return '

This is a Flask web application.

'

После добавления этого нового декоратора вы можете получить доступ к странице индекса как по адресу http://127.0.0.1:5000/, так и по адресу http://127.0.0.1:5000/index.

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

Кроме этого, хотелось бы разобрать динамические маршруты, то есть мы создадим маршрут, в котором слова, переданные через URL, будут написаны с заглавной буквы, и маршрут, который складывает два числа и отображает результат.

Для этого, открываем наш app1.py и добавляем:

from markupsafe import escape
from flask import Flask

# ...

@app.route('/capitalize//')
def capitalize(word):
    return '

{}

'.format(escape(word.capitalize()))

Этот новый маршрут имеет переменную секцию. Это говорит Flask взять значение из URL-адреса и передать его функции просмотра. Переменная URL передает аргумент ключевого слова функции просмотра capitalize(). Аргумент имеет то же имя, что и переменная URL (в данном случае слово). При этом вы можете получить доступ к слову, переданному через URL-адрес, и ответить его версией с заглавной буквы, используя метод capitalize() в Python.

Мы используем функцию escape (), которую мы импортировали, чтобы отобразить строку слова как текст. Это важно, чтобы избежать атак с использованием межсайтовых сценариев (XSS). Если пользователь отправляет вредоносный код JavaScript вместо слова, escape() отобразит его как текст, а браузер не запустит его, сохраняя безопасность вашего веб приложения.
Чтобы отобразить слово с заглавной буквы внутри заголовка HTML h1, используйте метод Python .format().

Запустив сервер разработки, открыв браузер и передав по следующим URL-адресам. Вы можете заменить выделенные слова любым словом по вашему выбору. Например:

http://127.0.0.1:5000/capitalize/hello
http://127.0.0.1:5000/capitalize/flask
http://127.0.0.1:5000/capitalize/python

Также можно использовать несколько переменных в маршруте. Чтобы продемонстрировать это, необходимо добавить маршрут, который складывает два положительных целых числа и отображает результат.
Откроем файл app1.py для редактирования и добавим в конец файла следующий маршрут:

# ...

@app.route('/add///')
def add(n1, n2):
    return '

{}

'.format(n1 + n2)

В этом маршруте вы используете специальный преобразователь int с переменной URL (/add///), который принимает только положительные целые числа. По умолчанию переменные URL-адреса считаются строками и рассматриваются как таковые.

Запустив сервер разработки, открыв браузер и перейдя по следующему URL-адресу:

http://127.0.0.1:5000/add/5/5/

мы увидим следующий результат:

При разработке веб приложения вы часто будете сталкиваться с ситуациями, когда приложение отображает ошибку вместо ожидаемого поведения. Вы можете неправильно написать переменную или забыть определить или импортировать функцию. Чтобы упростить решение этих проблем, Flask предоставляет отладчик при запуске приложения в режиме разработки. На этом шаге вы узнаете, как исправить ошибки в вашем приложении с помощью отладчика Flask.

Чтобы продемонстрировать, как обрабатывать ошибки, вы создадите маршрут, который приветствует пользователя из списка имен пользователей.
Откроем файл app1.py для редактирования и добавим в конец файла следующий маршрут:

# ...

@app.route('/users//')
def greet_user(user_id):
    users = ['Bob', 'Jane', 'Adam']
    return '

Hi {}

'.format(users[user_id])

В приведенном выше маршруте функция просмотра greet_user() получает аргумент user_id из переменной URL user_id. Мы используем преобразователь int, чтобы принимать положительные целые числа. Внутри функции у нас есть список Python под названием users, который содержит три строки, представляющие имена пользователей. Функция просмотра возвращает строку, которая создается в зависимости от предоставленного user_id. Если user_id равен 0, ответом будет Привет, Боб в теге h2, потому что Боб является первым элементом в списке (значение users[0]).

Запустив сервер разработки, откроем браузер и перейдем по следующим URL-адресам:

http://127.0.0.1:5000/users/0
http://127.0.0.1:5000/users/1
http://127.0.0.1:5000/users/2

Мы получим следующие ответы:

Hi Bob
Hi Jane
Hi Adam

Пока всё работает хорошо, но может пойти не так, если мы запросим приветствие для пользователя, которого не существует. Чтобы продемонстрировать, как работает отладчик Flask, наберем следующий URL-адрес:

http://127.0.0.1:5000/users/3

Вверху на странице указано имя исключения Python, которым является IndexError, что указывает на то, что индекс списка (в данном случае 3) находится за пределами диапазона списка (который составляет только от 0 до 2, потому что в списке всего три элемента). В этом случае вы можете использовать простую команду try ... except, чтобы исправить эту ошибку. Если запрошенный URL-адрес имеет индекс за пределами диапазона списка, пользователь получит ошибку 404 Not Found, которая является ошибкой HTTP, которая сообщает пользователю, что страница, которую он ищет, не существует.

Откроем файл app1.py для редактирования. Чтобы ответить с ошибкой HTTP 404, нам понадобится функция Flask abort(), которую можно использовать для создания ответов об ошибках HTTP. Изменим вторую строку файла, чтобы также импортировать эту функцию:

from markupsafe import escape
from flask import Flask, abort

Затем отредактируем функцию просмотра greet_user(), чтобы она выглядела следующим образом:

# ...

@app.route('/users//')
def greet_user(user_id):
    users = ['Bob', 'Jane', 'Adam']
    try:
        return '

Hi {}

'.format(users[user_id]) except IndexError: abort(404)

Мы использовали попытку выше, чтобы проверить возвращаемое выражение на наличие ошибок. Если ошибки не было, то есть user_id имеет значение, соответствующее индексу в списке пользователей, приложение ответит соответствующим приветствием. Если значение user_id выходит за пределы диапазона списка, возникает исключение IndexError,
и мы используем except, чтобы поймать ошибку и ответить ошибкой HTTP 404 с помощью вспомогательной функции Flask abort().

Теперь, когда сервер разработки запущен, снова посетим тот URL-адрес. На этот раз мы увидим стандартную страницу с ошибкой 404, информирующую пользователя о том, что страница не существует.

Проделав все эти действия, мы имеем общее представление о том, что такое Flask, как его установить и как использовать для написания веб приложения, как запустить сервер разработки и как использовать маршруты и функции просмотра для отображения различных веб страниц, обслуживающих определенные целей. Мы также узнали, как использовать динамические маршруты, чтобы позволить пользователям взаимодействовать с вашим веб приложением через URL-адрес, и как использовать отладчик для устранения ошибок.

Как использовать шаблоны в приложении Flask

1. Визуализация шаблона и использование переменных

Создав просто веб-приложение на Flask, я решил попробовать использование шаблонов. Для этого, я создал файл app2.py.

В него добавляем следующее:

from flask import Flask, render_template

app = Flask(__name__)


@app.route('/')
def hello():
    return render_template('index.html')

В этом блоке кода мы импортируем класс Flask и функцию render_template() из пакета flask. Мы используем класс Flask для создания экземпляра приложения Flask с именем app. Затем мы определяем функцию просмотра (которая является функцией Python, которая возвращает ответ HTTP) с именем hello(), используя декоратор app.route(), который преобразует обычную функцию в функцию просмотра. Эта функция представления использует функцию render_template() для визуализации файла шаблона с именем index.html.

Затем нам нужно будет создать файл шаблона index.html в каталоге с именем templates. Flask ищет шаблоны в каталоге templates, который называется шаблонами, поэтому имя важно. Для этого выполним следующую команду, чтобы создать каталог шаблонов:

mkdir templates

Затем откроем файл с именем index.html и добавим следующий HTML код:




    
    FlaskApp


    

Hello World!

Welcome to FlaskApp!

Здесь мы устанавливаем заголовок, добавляем Hello World! сообщение в виде заголовка h1 и создаем сообщение Welcome to FlaskApp! как заголовок h2. Далее запускаем наше приложение. (как это сделать говорилось выше)

Оставим сервер запущенным и откроем файл app2.py для редактирования. Импортируем модуль datetime из стандартной библиотеки Python и отредактируем функцию index(), чтобы файл выглядел следующим образом:

import datetime
from flask import Flask, render_template

app = Flask(__name__)


@app.route('/')
def hello():
    return render_template('index.html', utc_dt=datetime.datetime.utcnow())

Здесь мы импортировали модуль datetime и передали в шаблон index.html переменную с именем utc_dt со значением datetime.datetime.utcnow(), которое является текущей датой и временем в формате UTC.

Затем, чтобы отобразить значение переменной на странице индекса, откроем файл index.html для редактирования. Отредактируем файл, чтобы он выглядел следующим образом:




    
    FlaskApp


    

Hello World!

Welcome to FlaskApp!

{{ utc_dt }}

Мы добавили заголовок h2 со специальным разделителем {{...}} для печати значения переменной utc_dt. Откроем браузер и перейдём на главную страницу:

Теперь мы создали страницу индекса с шаблоном HTML в приложении Flask, отрисовали шаблон, передали и отобразили значение переменной. Затем мы избежим повторения кода, используя наследование шаблонов.

2. Использование механизма наследования шаблонов

На этом этапе мы создадим базовый шаблон с содержанием, которым можно будет поделиться с другими шаблонами. Мы отредактируем свой шаблон индекса, чтобы он унаследовал от базового шаблона. Затем мы создадим новую страницу, которая будет служить страницей «О приложении», где пользователи смогут найти дополнительную информацию о вашем приложении.
Для этого, создадим base.html и пропишем следующий код:




    
    {% block title %} {% endblock %} - FlaskApp
    
        nav a {
            color: #d64161;
            font-size: 3em;
            margin-left: 50px;
            text-decoration: none;
        }
    


    
    
{% block content %} {% endblock %}

Большая часть кода в этом файле представляет собой стандартный HTML код, заголовок, некоторые стили для ссылок навигации, панель навигации с двумя ссылками, одна для страницы индекса, другая для еще не созданной страницы «О программе», а также div для содержание страницы. (Ссылки пока не работают; на следующем шаге будет показано, как создавать ссылки между страницами). Однако, следующие выделенные части относятся к шаблонизатору Jinja:

•{% block title %} {% endblock %}: блок, который служит заполнителем для заголовка. Позже вы будете использовать его в других шаблонах, чтобы задать собственный заголовок для каждой страницы в приложении, не переписывая каждый раз весь раздел.
•{% block content %} {% endblock %}: другой блок, который будет заменен содержимым в зависимости от дочернего шаблона (шаблона, наследуемого от base.html), который заменит его.
Теперь, когда у нас есть базовый шаблон, мы можем воспользоваться им, используя наследование. Откроем файл index.html и затем заменим его содержимое следующим:

{% extends 'base.html' %}

{% block content %}
    

{% block title %} Index {% endblock %}

Hello World!

Welcome to FlaskApp!

{{ utc_dt }}

{% endblock %}

Здесь мы используем тег {% extends %} для наследования от шаблона base.html. Затем мы расширяем его, заменяя блок содержимого в базовом шаблоне тем, что находится внутри блока содержимого в предыдущем блоке кода.

Этот блок содержимого содержит тег h1 с текстовым индексом внутри основной надписи, который, в свою очередь, заменяет исходную основную надпись в шаблоне base.html текстовым индексом, так что полный заголовок становится индексом — FlaskApp. Таким образом, мы можем избежать повторения одного и того же текста дважды, поскольку он работает как заголовок страницы, так и заголовок, который появляется под панелью навигации, унаследованной от базового шаблона. Затем у нас есть еще несколько заголовков: один заголовок h1 с текстом Hello World!, заголовок h2 и заголовок h3, содержащий значение переменной utc_dt.

Наследование шаблонов дает нам возможность повторно использовать HTML код, который есть в других шаблонах (в данном случае base.html), без необходимости повторять его каждый раз, когда это необходимо. Сохраним и обновим страницу индекса в браузере. Страница будет выглядеть следующим образом:

Затем мы создаем страницу «About». Открываем файл app2.py, чтобы добавить новый маршрут и добавляем следующее:

# ...
@app.route('/about/')
def about():
return render_template('about.html')

Здесь используется декоратор app.route() для создания функции просмотра с именем about(). В нем мы возвращаем результат вызова функции render_template() с именем файла шаблона about.html в качестве аргумента.

Откроем файл шаблона с именем about.html для редактирования и добавьте следующий код:

{% extends 'base.html' %}
{% block content %}

{% block title %} About {% endblock %}

FlaskApp is a Flask web application written in Python.

{% endblock %}

Здесь мы наследуем базовый шаблон с помощью тега extends, заменяем блок содержимого базового шаблона тегом h1, который также служит заголовком страницы, и добавляем тег h3 с некоторой информацией о приложении.

Когда сервер разработки запущен, перейдем по следующему URL адресу в своем браузере:

http://127.0.0.1:5000/about

Мы увидим следующее:

Мы создали базовый шаблон и использовали его на своей индексной странице и на странице сведений, чтобы избежать повторения кода.

3. Связывание страниц

На этом этапе мы свяжем страницы в шаблонах с помощью вспомогательной функции url_for(). Мы добавим две ссылки на панель навигации в свой базовый шаблон, одну для главной страницы и другую для страницы «About».

Сначала откроем базовый шаблон для редактирования и внесите следующие изменения:





{% block title %} {% endblock %} - FlaskApp

nav a {
color: #d64161;
font-size: 3em;
margin-left: 50px;
text-decoration: none;
}





{% block content %} {% endblock %}

Здесь мы используем специальную функцию url_for(), которая возвращает URL адрес функции просмотра, которую мы ему передаем. Первая ссылка ссылается на маршрут функции просмотра hello() (которая является страницей индекса). Вторая ссылка указывает на маршрут функции просмотра about(). Обратите внимание, что мы передаем имя функции просмотра, а не маршрут (/ или /about). Использование функции url_for() для создания URL адресов помогает лучше управлять URL адресами. Если мы жестко запрограммируем URL адреса, наши ссылки сломаются, если мы изменим маршруты. С url_for() мы можем редактировать маршруты и гарантировать, что ссылки будут работать должным образом. Функция url_for() также заботится о других вещах, таких как экранирование специальных символов.

Теперь перейдем на главную страницу и попробуем ссылки на панели навигации. Мы увидим, что они работают.

4. Использование условных выражений и циклов

На этом этапе мы будем использовать операторы if в своих шаблонах, чтобы управлять тем, что отображать в зависимости от определенных условий. Мы также будем использовать циклы for для просмотра списков Python и отображения каждого элемента в списке. Мы добавим новую страницу, на которой будут отображаться комментарии в виде списка. Комментарии с четным порядковым номером будут иметь синий фон, а комментарии с нечетным порядковым номером будут отображаться на сером фоне.

Сначала мы создадим маршрут для страницы комментариев. Откроем файл app2.py для редактирования и добавьте следующий маршрут в конец файла:

# ...
@app.route('/comments/')
def comments():
comments = ['This is the first comment.',
'This is the second comment.',
'This is the third comment.',
'This is the fourth comment.'
]
return render_template('comments.html', comments=comments)

В приведенном выше маршруте у нас есть список Python, называемый комментариями, который содержит четыре элемента. Мы возвращаете файл шаблона с именем comments.html в последней строке, передача переменной с именем comments, содержащей список, в файл шаблона.

Затем создадим и откроем новый файл comments.html в каталоге шаблонов для редактирования и добавим следующий код:

{% extends 'base.html' %}
{% block content %}

{% block title %} Comments {% endblock %}

{% for comment in comments %}

{{ comment }}

{% endfor %}
{% endblock %}

Здесь мы расширяем шаблон base.html и заменяем содержимое блока содержимого. Во-первых, мы используем заголовок h1, который также служит заголовком страницы.
Мы используем цикл Jinja for в строке {% for comment in comments %}, чтобы просмотреть каждый комментарий в списке комментариев (который сохраняется в переменной комментария). Комментарий отображается в теге (p style="font-size: 24px" {{comment}} /p) так же, как мы обычно отображаем переменную в Jinja. Мы сигнализируем о завершении цикла for с помощью ключевого слова {% endfor %}. Это отличается от способа построения циклов for в Python, поскольку в шаблонах Jinja нет специального отступа.

Когда сервер разработки запущен, откроем браузер и перейдем на страницу комментариев:

http://127.0.0.1:5000/comments

Мы увидим следующее:

Давайте откроем файл шаблона comments.html и отредактируйте его, чтобы он выглядел следующим образом:

{% extends 'base.html' %}

{% block content %}
    

{% block title %} Comments {% endblock %}

{% for comment in comments %} {% if loop.index %2 == 0 %} {% set bg_color = '#e6f9ff' %} {% else %} {% set bg_color = '#eee' %} {% endif %}

#{{ loop.index }}

{{ comment }}

{% endfor %}
{% endblock %}

С помощью этого нового редактирования мы добавили оператор if в строку {% if loop.index %2 == 0 %}. Переменная цикла — это специальная переменная Jinja, которая дает нам доступ к информации о текущем цикле. Здесь мы используем loop.index, чтобы получить индекс текущего элемента, который начинается с 1, а не с 0, как в списках Python. Оператор if здесь проверяет, использует ли индекс даже оператор %. Он проверяет остаток от деления номера индекса на 2; если остаток равен 0, это означает, что номер индекса четный, в противном случае номер индекса нечетный. Тег {% set %} используется для объявления переменной с именем bg_color. Если порядковый номер четный, мы устанавливаем его на голубоватый цвет, в противном случае, если номер индекса нечетный, мы устанавливаем переменную bg_color на серый. Затем мы используем переменную bg_color, чтобы установить цвет фона для тега div, содержащего комментарий. Над текстом комментария мы используем loop.index для отображения текущего номера индекса в теге p.

Сохраним и посмотрим результат:

Кроме того, чтобы отобразить все комментарии, кроме второго, мы можем использовать оператор if с условием loop.index != 2, чтобы отфильтровать второй комментарий.

Откроем шаблон комментариев и отредактируем его, чтобы он выглядел следующим образом:

{% extends 'base.html' %}
{% block content %}

{% block title %} Comments {% endblock %}

{% for comment in comments %} {% if loop.index != 2 %}

#{{ loop.index }}

{{ comment }}

{% endif %} {% endfor %}
{% endblock %}

Здесь мы используем {% if loop.index != 2 %}, чтобы отображать только те комментарии, которые не имеют индекса 2, что означает все комментарии, кроме второго. Мы также используем жестко запрограммированное значение для цвета фона вместо помощника loop.cycle(), чтобы упростить задачу, а остальное не изменяется. Мы завершаем оператор if, используя {% endif %}. Обновим страницу комментариев, и мы увидим, что второй комментарий не отображается.

Теперь нам нужно добавить ссылку, которая переводит пользователей на страницу комментариев на панели навигации. Откроем базовый шаблон для редактирования и изменим содержимое тега nav, добавив к нему новую ссылку a:




    
    {% block title %} {% endblock %} - FlaskApp
    
        nav a {
            color: #d64161;
            font-size: 3em;
            margin-left: 50px;
            text-decoration: none;
        }
    


    
    
{% block content %} {% endblock %}

Здесь мы используем помощник url_for() для ссылки на функцию просмотра comments(). Теперь на панели навигации будет новая ссылка, которая ведет на страницу комментариев.

5. Использование фильтров

На этом этапе мы узнаем, как использовать фильтры Jinja в своих шаблонах.

Сначала мы преобразуем комментарии на странице комментариев в верхний регистр. Откроем для редактирования шаблон comments.html и отредактируем его, чтобы он выглядел следующим образом:

{% extends 'base.html' %}
{% block content %}

{% block title %} Comments {% endblock %}

{% for comment in comments %} {% if loop.index != 2 %}

#{{ loop.index }}

{{ comment | upper }}

{% endif %} {% endfor %}
{% endblock %}

Здесь мы расширяем шаблон base.html и заменяем содержимое блока содержимого. Во-первых, мы используем заголовок h1, который также служит заголовком страницы.
Мы используем цикл Jinja for в строке {% for comment in comments %}, чтобы просмотреть каждый комментарий в списке комментариев (который сохраняется в переменной комментария). Комментарий отображается в теге ((p style = font-size: 24px {{comment}} /p)) так же, как мы обычно отображаем переменную в Jinja. Мы сигнализируем о завершении цикла for с помощью ключевого слова {% endfor %}. Это отличается от способа построения циклов for в Python, поскольку в шаблонах Jinja нет специального отступа.

Когда сервер разработки запущен, откроем браузер и перейдем на страницу комментариев:

http://127.0.0.1:5000/comments

Фильтры также могут принимать аргументы в круглых скобках. Чтобы продемонстрировать это, воспользуемся фильтром объединения, чтобы объединить все комментарии в списке комментариев.

Откроем шаблон комментариев и отредактируем его, чтобы он выглядел следующим образом:

{% extends 'base.html' %}
{% block content %}

{% block title %} Comments {% endblock %}

{% for comment in comments %} {% if loop.index != 2 %}

#{{ loop.index }}

{{ comment | upper }}

{% endif %} {% endfor %}

{{ comments | join(" | ") }}

{% endblock %}

Здесь мы добавили теги hr и div, где мы объединяем все комментарии в списке комментариев с помощью фильтра join(). Обновим страницу комментариев, и мы увидим страницу, подобную следующей:

Как видите, список comments отображается с комментариями, разделенными вертикальной чертой, которую мы передали фильтру join().

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

{% extends 'base.html' %}

{% block content %}
    

{% block title %} Comments {% endblock %}

{% for comment in comments %} {% if loop.index != 2 %}

#{{ loop.index }}

{{ comment | upper }}

{% endif %} {% endfor %}
{{ "

COMMENTS

" }}

{{ comments | join("


") }}

{% endblock %}

Здесь мы добавили значение h1 COMMENTS/h1 и изменили аргумент соединения на тег hr. Обновим страницу комментариев, и мы увидите страницу, подобную следующей:

Чтобы отобразить HTML теги выше, откроем файл шаблона комментариев и добавим безопасный фильтр:

{% extends 'base.html' %}
{% block content %}

{% block title %} Comments {% endblock %}

{% for comment in comments %} {% if loop.index != 2 %}

#{{ loop.index }}

{{ comment | upper }}

{% endif %} {% endfor %}
{{ "

COMMENTS

" | safe }}

{{ comments | join("


") | safe }}

{% endblock %}

Мы можем видеть, что мы также можем объединить фильтры в цепочку, как в строке p{{ comments | join("hr") | safe }}hr. Каждый фильтр применяется к результату предыдущей фильтрации. Обновим страницу комментариев, и мы увидим, что теги HTML теперь отображаются должным образом:

6 — интеграция Bootstrap

Сейчас мы узнаем, как использовать набор инструментов Bootstrap для стилизации нашего приложения. Добавим панель навигации Bootstrap в базовый шаблон, которая будет отображаться на всех страницах, унаследованных от базового шаблона.

Чтобы использовать Bootstrap, нужно добавить его в базовый шаблон, чтобы мы могли использовать его во всех других шаблонах. Откроем свой шаблон base.html для редактирования и запишем:



  
    
    
    

    
    

    {% block title %} {% endblock %} - FlaskApp
  
  
    
    
{% block content %} {% endblock %}

Большая часть приведенного выше кода — это шаблон Bootstrap, необходимый для его использования. У нас есть несколько метатегов, ссылка на файл CSS Bootstrap в разделе head, а внизу у нас есть ссылка на необязательный JavaScript. Выделенные части кода содержат код Jinja, объясненный в предыдущих шагах.

Обратите внимание, как мы используем определенные теги и классы CSS, чтобы указать Bootstrap, как отображать каждый элемент.

В теге nav выше у нас есть тег a с классом navbar-brand, который определяет ссылку бренда на панели навигации. Внутри тега ul class = ".navbar-nav" у нас есть обычные элементы панели навигации внутри тега a в теге li.

Когда сервер разработки запущен, откроем страницу индекса в своем браузере:

http://127.0.0.1:5000/

Мы увидим страницу, похожую на следующую:

Теперь мы знаем, как использовать HTML шаблоны в своем веб-приложении Flask. Мы использовали переменные для передачи данных с сервера в шаблоны, чтобы избежать повторения HTML кода мы использовали наследование шаблонов, встроили такие элементы, как условные выражения if и циклы for, а также ссылки между разными страницами. Мы узнали о фильтрах для изменения текста и отображения надежного HTML, а также интегрировали Bootstrap в свое приложение.

Рецензия на перевод статьи "Flask на примере обновления пользовательского интерфейса"

Автор перевода: Ускова Екатерина, ЭУ-220.

В данной статье рассказывается о работе над пользовательским интерфейсом приложения Flask,которое будет вычислять пары частот слов на основе текста из заданного URL-адреса. Данное руководство поможет разобраться в оптимизации интерфейсов приложений Flask, необходимых для удобной и комфортной работы пользователя.

В ходе прочтения смысловых и грамматических ошибок не обнаружено. Ссылка на оригинальную статью есть. Имеется также правильное и приятное для чтения оформление самого перевода, где в самом начале идет название темы, а после оглавление, в котором отображается содержание статьи с ссылками на эти пункты. Само оформление кодов реализовано очень удобно для читателя: они заметны, их сразу видно в статье; имеется возможность копирования всего кода сразу; возможность открыть код в новом окне браузера. Все коды в переводе объяснены, а их отдельно взятые элементы выделены в тексте жирным шрифтом. Екатерина выполнила работу в необходимом объеме и в указанные сроки. Порекомендовал бы поставить оценку “отлично”.

Разработка интерфейсов визуализации данных на Python с помощью Dash

Прежде чем приступить к выполнению работы, давайте разберем, что же такое Dash? Dash — это платформа с открытым исходным кодом для создания интерфейсов визуализации данных, помогающая специалистам по обработке данных создавать аналитические веб-приложения, не требуя передовых знаний в области веб-разработки.

Я всегда работаю в PyCharm, поэтому, прежде чем приступить к работе, я создаю проект, в котором создается виртуальная среда. Далее, я добавляю в проект файл, с которым будет происходить работа, а именно: avocado.csv, содержащий набор данных о продажах и ценах на авокадо в США за период с 2015 по 2018 годы. Скачать данный файл можно здесь. После этого, необходимо установить следующие библиотеки: dash и pandas. Теперь мы можем приступать к работе!

Создание первого приложения

Давайте создадим пустой файл с именем app1.py в корневом каталоге нашего проекта и создадим наше первое приложение:

import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd

data = pd.read_csv("avocado.csv")
data = data.query("type == 'conventional' and region == 'Albany'")
data["Date"] = pd.to_datetime(data["Date"], format="%Y-%m-%d")
data.sort_values("Date", inplace=True)

app = dash.Dash(__name__)

app.layout = html.Div(
    children=[
        html.H1(
            children="Avocado Analytics",
            style={"fontSize": "48px", "color": "red"},
        ),
        html.P(
            children="Analyze the behavior of avocado prices"
            " and the number of avocados sold in the US"
            " between 2015 and 2018",
        ),
        dcc.Graph(
            figure={
                "data": [
                    {
                        "x": data["Date"],
                        "y": data["AveragePrice"],
                        "type": "lines",
                    },
                ],
                "layout": {"title": "Average Price of Avocados"},
            },
        ),
        dcc.Graph(
            figure={
                "data": [
                    {
                        "x": data["Date"],
                        "y": data["Total Volume"],
                        "type": "lines",
                    },
                ],
                "layout": {"title": "Avocados Sold"},
            },
        ),
    ]
)

if __name__ == "__main__":
    app.run_server(debug=True)

В строках с 1 по 4 мы импортируем необходимые библиотеки: dash, dash_core_components, dash_html_components и pandas. Каждая библиотека предоставляет стандартный блок для вашего приложения:
• dash поможет нам инициализировать ваше приложение.
• dash_core_components позволяет создавать интерактивные компоненты, такие как графики, раскрывающиеся списки или диапазоны дат.
• dash_html_components позволяет получить доступ к тегам HTML.
• pandas помогает читать и систематизировать данные.

В строках с 6 по 9 мы считываем данные и предварительно обрабатываем их для использования на панели управления. Мы фильтруем некоторые данные, потому что текущая версия нашей информационной панели не является интерактивной и в противном случае значения на графике не имели бы смысла. В строке 11, мы создаем экземпляр класса Dash. Если вы раньше использовали Flask, то инициализация класса Dash может показаться вам знакомой. В Flask вы обычно инициализируете приложение WSGI с помощью Flask(__name__). Точно так же для приложения Dash вы используете Dash(__name__).

Далее, мы определяем свойство макета нашего приложения, которое определяет внешний вид нашего приложения. В нашем случае мы будем использовать заголовок с описанием под ним и двумя графиками В строках с 13 по 23 вы можете увидеть на практике компоненты Dash HTML. Мы начинаем с определения родительского компонента, html.Div. Затем мы добавляем еще два элемента, заголовок (html.H1) и абзац (html.P) в качестве его дочерних элементов. Эти компоненты эквивалентны div, HTML-теги h1 и p. Вы можете использовать аргументы компонентов для изменения атрибутов или содержимого тегов. Например, чтобы указать, что находится внутри тега div, мы используем аргумент children в html.Div. В компонентах также есть другие аргументы, такие как style, className или id, которые относятся к атрибутам тегов HTML. При этом, если вы хотите изменить размер и цвет элемента H1, то достаточно прописать изменения через style, как это сделал я в 17 строке.

В строках 24–50 фрагмента кода макета вы можете увидеть компонент графика из Dash Core Components на практике. В app.layout есть два компонента dcc.Graph. Первый отображает средние цены на авокадо за период исследования, а второй — количество авокадо, проданных в США за тот же период. Под капотом Dash использует Plotly.js для создания графиков. Компоненты dcc.Graph ожидают объект фигуры или словарь Python, содержащий данные графика и макет. В этом случае вы предоставляете последнее.
И последние две строчки кода позволяют запускать приложение Dash локально, используя встроенный сервер Flask. Далее, мы запускаем наше приложение, после чего переходим по высветившейся ссылке и смотрим результат:

Создание второго приложения с добавлением внешних ресурсов

В данной главе вы узнаете, как настроить внешний вид панели инструментов, а именно:
• Добавление значка и заголовка на страницу.
• Изменение семейства шрифтов вашей панели инструментов.
• Использование внешних файлов CSS для стилизации компонентов Dash.

Сначала, в папке с проектом, мы создаем еще одну папку с именем assets, в которую добавляем скаченный значок с именем favicon.ico (скачать его можно здесь) и создаем файл style.css, содержащий следующие команды:

body {
    font-family: "Lato", sans-serif;
    margin: 0;
    background-color: #F7F7F7;
}

.header {
    background-color: #222222;
    height: 256px;
    display: flex;
    flex-direction: column;
    justify-content: center;
}

.header-emoji {
    font-size: 48px;
    margin: 0 auto;
    text-align: center;
}

.header-title {
    color: #FFFFFF;
    font-size: 48px;
    font-weight: bold;
    text-align: center;
    margin: 0 auto;
}

.header-description {
    color: #CFCFCF;
    margin: 4px auto;
    text-align: center;
    max-width: 384px;
}

.wrapper {
    margin-right: auto;
    margin-left: auto;
    max-width: 1024px;
    padding-right: 10px;
    padding-left: 10px;
    margin-top: 32px;
}

.card {
    margin-bottom: 24px;
    box-shadow: 0 4px 6px 0 rgba(0, 0, 0, 0.18);
}

Файлы в папке assets содержат стили, которые мы применим к компонентам в макете приложения. После запуска сервера, Dash автоматически обслуживает файлы, расположенные в этой папке. И для установки значка по умолчанию нам не нужно предпринимать никаких дополнительных действий. Для использования стилей, определенных в style.css, нам необходимо использовать аргумент className в компонентах Dash. Поэтому, давайте создадим новый файл app2.py, в котором пропишем следующий код:

import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd

data = pd.read_csv("avocado.csv")
data = data.query("type == 'conventional' and region == 'Albany'")
data["Date"] = pd.to_datetime(data["Date"], format="%Y-%m-%d")
data.sort_values("Date", inplace=True)

external_stylesheets = [
    {
        "href": "https://fonts.googleapis.com/css2?"
        "family=Lato:wght@400;700&display=swap",
        "rel": "stylesheet",
    },
]
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.title = "Avocado Analytics: Understand Your Avocados!"

app.layout = html.Div(
    children=[
        html.Div(
            children=[
                html.P(children="🥑", className="header-emoji"),
                html.H1(
                    children="Avocado Analytics", className="header-title"
                ),
                html.P(
                    children="Analyze the behavior of avocado prices"
                    " and the number of avocados sold in the US"
                    " between 2015 and 2018",
                    className="header-description",
                ),
            ],
            className="header",
        ),
        html.Div(
            children=[
                html.Div(
                    children=dcc.Graph(
                        id="price-chart",
                        config={"displayModeBar": False},
                        figure={
                            "data": [
                                {
                                    "x": data["Date"],
                                    "y": data["AveragePrice"],
                                    "type": "lines",
                                    "hovertemplate": "$%{y:.2f}"
                                                     "",
                                },
                            ],
                            "layout": {
                                "title": {
                                    "text": "Average Price of Avocados",
                                    "x": 0.05,
                                    "xanchor": "left",
                                },
                                "xaxis": {"fixedrange": True},
                                "yaxis": {
                                    "tickprefix": "$",
                                    "fixedrange": True,
                                },
                                "colorway": ["#17B897"],
                            },
                        },
                    ),
                    className="card",
                ),
                html.Div(
                    children=dcc.Graph(
                        id="volume-chart",
                        config={"displayModeBar": False},
                        figure={
                            "data": [
                                {
                                    "x": data["Date"],
                                    "y": data["Total Volume"],
                                    "type": "lines",
                                },
                            ],
                            "layout": {
                                "title": {
                                    "text": "Avocados Sold",
                                    "x": 0.05,
                                    "xanchor": "left",
                                },
                                "xaxis": {"fixedrange": True},
                                "yaxis": {"fixedrange": True},
                                "colorway": ["#E12D39"],
                            },
                        },
                    ),
                    className="card",
                ),
            ],
            className="wrapper",
        ),
    ]
)

if __name__ == "__main__":
    app.run_server(debug=True)

Код точно такой же, как и в app1.py, но с некоторыми изменениями. В строках с 21 по 37 вы можете увидеть, что в исходную версию панели мониторинга были внесены два изменения:
1. Появился новый элемент абзаца с эмодзи авокадо, который будет служить логотипом.
2. В каждом компоненте есть аргумент className. Эти имена классов должны соответствовать селектору классов в style.css, который будет определять внешний вид каждого компонента.
Например, класс header-description, назначенный компоненту абзаца, начинающемуся с «Analyze the behavior of avocado prices», имеет соответствующий селектор в style.css:

.header-description {
    color: #CFCFCF;
    margin: 4px auto;
    text-align: center;
    max-width: 384px;
}

Строки с 29 по 34 файла style.css определяют формат селектора класса описания заголовка. Они изменят цвет, поля, выравнивание и максимальную ширину любого компонента с className = "header-description". Все компоненты имеют соответствующие селекторы классов в файле CSS. Другое существенное изменение — графики. При построении графика цены, мы выполнили следующие изменения:
• Строка 43: мы удаляем плавающую полосу, которая отображается на графике по умолчанию.
• Строки 50 и 51: мы устанавливаем шаблон наведения таким образом, чтобы при наведении курсора на точку данных отображалась цена в долларах. Вместо 2,5он будет отображаться как 2,5 доллара.
• Строки с 54 по 66: мы настраиваем ось, цвет рисунка и формат заголовка в разделе макета графика.
• Строка 69: мы оборачиваем график в html.Div с классом «card». Это придаст графику белый фон и добавит небольшую тень под ним.
Аналогичные изменения внесены в графики продаж и объемов. После всех этих изменений наша панель управления должна выглядеть так:

Добавление интерактивности в приложения Dash с помощью обратных вызовов

В этом разделе мы будем добавлять интерактивные элементы на панель управления. Интерактивность Dash основана на парадигме реактивного программирования. Это означает, что вы можете связывать компоненты с элементами вашего приложения, которые хотите обновить. Если пользователь взаимодействует с компонентом ввода, например, с раскрывающимся списком или ползунком диапазона, то вывод, например, график, будет автоматически реагировать на изменения во вводе. Теперь давайте сделаем вашу панель управления интерактивной. Эта новая версия вашей панели инструментов позволит пользователю взаимодействовать со следующими фильтрами:
• Область
• Тип авокадо
• Диапазон дат
Поэтому, давайте создадим новый файл в папке assets с имеем style2.css и пропишем следующие команды:

 
body {
    font-family: "Lato", sans-serif;
    margin: 0;
    background-color: #F7F7F7;
}

.header {
    background-color: #222222;
    height: 288px;
    padding: 16px 0 0 0;
}

.header-emoji {
    font-size: 48px;
    margin: 0 auto;
    text-align: center;
}

.header-title {
    color: #FFFFFF;
    font-size: 48px;
    font-weight: bold;
    text-align: center;
    margin: 0 auto;
}

.header-description {
    color: #CFCFCF;
    margin: 4px auto;
    text-align: center;
    max-width: 384px;
}

.wrapper {
    margin-right: auto;
    margin-left: auto;
    max-width: 1024px;
    padding-right: 10px;
    padding-left: 10px;
    margin-top: 32px;
}

.card {
    margin-bottom: 24px;
    box-shadow: 0 4px 6px 0 rgba(0, 0, 0, 0.18);
}

.menu {
    height: 112px;
    width: 912px;
    display: flex;
    justify-content: space-evenly;
    padding-top: 24px;
    margin: -80px auto 0 auto;
    background-color: #FFFFFF;
    box-shadow: 0 4px 6px 0 rgba(0, 0, 0, 0.18);
}

.Select-control {
    width: 256px;
    height: 48px;
}

.Select--single > .Select-control .Select-value, .Select-placeholder {
    line-height: 48px;
}

.Select--multi .Select-value-label {
    line-height: 32px;
}

.menu-title {
    margin-bottom: 6px;
    font-weight: bold;
    color: #079A82;
}

После этого, мы создаем еще один файл, но уже просто в папке с проектом, с именем app3.py, в котором прописываем следующий код:

import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import numpy as np
from dash.dependencies import Output, Input

data = pd.read_csv("avocado.csv")
data["Date"] = pd.to_datetime(data["Date"], format="%Y-%m-%d")
data.sort_values("Date", inplace=True)

external_stylesheets = [
    {
        "href": "https://fonts.googleapis.com/css2?"
        "family=Lato:wght@400;700&display=swap",
        "rel": "stylesheet",
    },
]
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.title = "Avocado Analytics: Understand Your Avocados!"

app.layout = html.Div(
    children=[
        html.Div(
            children=[
                html.P(children="🥑", className="header-emoji"),
                html.H1(
                    children="Avocado Analytics", className="header-title"
                ),
                html.P(
                    children="Analyze the behavior of avocado prices"
                    " and the number of avocados sold in the US"
                    " between 2015 and 2018",
                    className="header-description",
                ),
            ],
            className="header",
        ),
        html.Div(
            children=[
                html.Div(
                    children=[
                        html.Div(children="Region", className="menu-title"),
                        dcc.Dropdown(
                            id="region-filter",
                            options=[
                                {"label": region, "value": region}
                                for region in np.sort(data.region.unique())
                            ],
                            value="Albany",
                            clearable=False,
                            className="dropdown",
                        ),
                    ]
                ),
                html.Div(
                    children=[
                        html.Div(children="Type", className="menu-title"),
                        dcc.Dropdown(
                            id="type-filter",
                            options=[
                                {"label": avocado_type, "value": avocado_type}
                                for avocado_type in data.type.unique()
                            ],
                            value="organic",
                            clearable=False,
                            searchable=False,
                            className="dropdown",
                        ),
                    ],
                ),
                html.Div(
                    children=[
                        html.Div(
                            children="Date Range",
                            className="menu-title"
                            ),
                        dcc.DatePickerRange(
                            id="date-range",
                            min_date_allowed=data.Date.min().date(),
                            max_date_allowed=data.Date.max().date(),
                            start_date=data.Date.min().date(),
                            end_date=data.Date.max().date(),
                        ),
                    ]
                ),
            ],
            className="menu",
        ),
        html.Div(
            children=[
                html.Div(
                    children=dcc.Graph(
                        id="price-chart", config={"displayModeBar": False},
                    ),
                    className="card",
                ),
                html.Div(
                    children=dcc.Graph(
                        id="volume-chart", config={"displayModeBar": False},
                    ),
                    className="card",
                ),
            ],
            className="wrapper",
        ),
    ]
)


@app.callback(
    [Output("price-chart", "figure"), Output("volume-chart", "figure")],
    [
        Input("region-filter", "value"),
        Input("type-filter", "value"),
        Input("date-range", "start_date"),
        Input("date-range", "end_date"),
    ],
)
def update_charts(region, avocado_type, start_date, end_date):
    mask = (
        (data.region == region)
        & (data.type == avocado_type)
        & (data.Date >= start_date)
        & (data.Date <= end_date)
    )
    filtered_data = data.loc[mask, :]
    price_chart_figure = {
        "data": [
            {
                "x": filtered_data["Date"],
                "y": filtered_data["AveragePrice"],
                "type": "lines",
                "hovertemplate": "$%{y:.2f}",
            },
        ],
        "layout": {
            "title": {
                "text": "Average Price of Avocados",
                "x": 0.05,
                "xanchor": "left",
            },
            "xaxis": {"fixedrange": True},
            "yaxis": {"tickprefix": "$", "fixedrange": True},
            "colorway": ["#17B897"],
        },
    }

    volume_chart_figure = {
        "data": [
            {
                "x": filtered_data["Date"],
                "y": filtered_data["Total Volume"],
                "type": "lines",
            },
        ],
        "layout": {
            "title": {"text": "Avocados Sold", "x": 0.05, "xanchor": "left"},
            "xaxis": {"fixedrange": True},
            "yaxis": {"fixedrange": True},
            "colorway": ["#E12D39"],
        },
    }
    return price_chart_figure, volume_chart_figure


if __name__ == "__main__":
    app.run_server(debug=True)

Данный код очень схож с app1 и app2, но он так же претерпел изменения. В строках с 24 по 74 мы определяем html.Div поверх наших графиков, состоящих из двух раскрывающихся списков и селектора диапазона дат. Он будет служить меню, которое пользователь будет использовать для взаимодействия с данными:

В строках с 41 по 55 мы определяем раскрывающийся список, который пользователи будут использовать для фильтрации данных по региону. Помимо заголовка, в нем есть компонент dcc.Dropdown. Вот что означает каждый из параметров:
• id — идентификатор элемента.
• options — это параметры, отображаемые при выборе раскрывающегося списка. Он ожидает словарь с метками и значениями.
• value — значение по умолчанию при загрузке страницы.
• clearable позволяет пользователю оставить это поле пустым, если установлено значение True.
• className — это селектор классов, используемый для применения стилей.
Селекторы «Type» и «Date Range» имеют ту же структуру, что и раскрывающееся меню «Region».

В строках с 90 по 106 мы определяем компоненты dcc.Graph. Возможно, вы заметили, что по сравнению с предыдущей версией панели инструментов в компонентах отсутствует аргумент figure. Это связано с тем, что аргумент figure теперь будет генерироваться функцией обратного вызова с использованием входных данных, которые пользователь устанавливает с помощью селекторов «Region», «Type» и «Date Range».

Но пока что мы только определили, как пользователь будет взаимодействовать с нашим приложением. Теперь нам нужно заставить ваше приложение реагировать на действия пользователя. Для этого вы воспользуетесь функциями обратного вызова. Функции обратного вызова Dash — это обычные функции Python с декоратором app.callback. В Dash при изменении ввода запускается функция обратного вызова. Функция выполняет некоторые заранее определенные операции, такие как фильтрация набора данных, и возвращает результат в приложение. По сути, обратные вызовы связывают входы и выходы в вашем приложении. Сама функция обратного вызова, используемая для обновления графиков, представлена со строки 111 по 164.

В строках 111–119 мы определяем входы и выходы внутри декоратора app.callback. Сначала мы определяем выходы с помощью объектов вывода. Эти объекты принимают два аргумента:
1. Идентификатор элемента, который они изменят при выполнении функции.
2. Свойство изменяемого элемента.
Например, Output("price-chart", "figure") обновит свойство figure элемента «price-chart». Затем мы определяем входы с помощью объектов Input. Они также принимают два аргумента:
1. Идентификатор элемента, за изменениями которого они будут следить.
2. Свойство наблюдаемого элемента, которое они должны принимать, когда происходит изменение.
Итак, Input("region-filter", "value") будет следить за элементом «region-filter» на предмет изменений и примет его свойство value, если элемент изменится.

В строке 120 мы определяем функцию, которая будет применяться при изменении ввода. Здесь следует отметить, что аргументы функции будут соответствовать порядку объектов Input, переданных в обратный вызов. Нет явной связи между именами аргументов в функции и значениями, указанными во входных объектах.

Наконец, в строках 121–164 вы определяете тело функции. В этом случае функция принимает входные данные (region, type of avocado и date range), фильтрует данные и генерирует объекты-фигуры для графиков цен и объемов.
Результат:

Исходя из всего проделанного выше, мы можем использовать Dash для создания аналитических приложений, что на данный момент очень востребовано и ценно в современном мире.

Интерактивная визуализация данных с использованием Bokeh

Bokeh гордится тем, что является библиотекой для интерактивной визуализации данных. В отличие от популярных аналогов в области визуализации Python, таких как Matplotlib и Seaborn, Bokeh отображает свою графику с помощью HTML и JavaScript. Это делает его отличным кандидатом для создания веб-панелей и приложений. Тем не менее, это не менее мощный инструмент для изучения и понимания ваших данных или создания красивых пользовательских диаграмм для проекта, или отчета.

Создание первой фигуры

Прежде чем приступить к работе, нам необходимо, соответственно, установить определенные библиотеки, а именно: pandas, numpy и bokeh. Всего есть несколько способов сделать инфографику в Bokeh:
1.output_file(‘filename.html’) - запишет инфографику в статический HTML-файл.
2.output_notebook() - отобразит инфографику непосредственно в Jupyter Notebook.
Важно отметить, что ни одна из функций не отображает визуализацию. Этого не произойдет, пока не будет вызвана функция show(). Однако они гарантируют, что при вызове show() визуализация появится там, где вы хотите. И так как я буду работать в привычном для меня PyCharm, то я буду использовать первый способ. Давайте же создадим первую фигуру:

from bokeh.io import output_file
from bokeh.plotting import figure, show

# Рисунок будет отображен в статическом HTML-файле
output_file('Создание_первой_фигуры.html',
            title='Empty Bokeh Figure')

# Настроить общий объект figure()
fig = figure()

# Посмотрите, как это выглядит
show(fig)

Результат:

Как видите, новое окно браузера открылось с вкладкой под названием Empty Bokeh Figure и пустой фигурой, а сам созданный файл с именем «Создание_первой_фигуры.html» появился в папке с проектом.

Подготовка фигуры к данным

Теперь, когда вы знаете, как создавать и просматривать общую фигуру Bokeh в браузере, пора узнать больше о том, как настроить объект figure(). Объект figure() — это не только основа визуализации данных, но и объект, который открывает все доступные инструменты Bokeh для инфографики. Figure Bokeh является подклассом объекта Bokeh Plot, который предоставляет многие параметры, которые позволяют настраивать эстетические элементы инфографики. Чтобы вы могли получить представление о доступных параметрах настройки, давайте создадим самую уродливую инфографика на свете:

from bokeh.plotting import figure, show

output_file('Подготовка_фигуры_к_данным.html',
            title='Bokeh Figure')


fig = figure(background_fill_color='gray',
             background_fill_alpha=0.5,
             border_fill_color='blue',
             border_fill_alpha=0.25,
             plot_height=300,
             plot_width=500,
             x_axis_label='X Label',
             x_axis_type='datetime',
             x_axis_location='above',
             x_range=('2018-01-01', '2018-06-30'),
             y_axis_label='Y Label',
             y_axis_type='linear',
             y_axis_location='left',
             y_range=(0, 100),
             title='Example Figure',
             title_location='right',
             toolbar_location='below',
             tools='save')


show(fig)

Результат:

Рисование данных с помощью глифов

Пустая фигура не так уж и интересна, поэтому давайте посмотрим на глифы. Глиф — это векторизованная графическая форма или маркер, который используется для представления ваших данных в виде круга или квадрата. Начнем с очень простого примера, нарисовав несколько точек на координатной сетке x-y:

#РИСОВАНИЕ ДАННЫХ С ПОМОЩЬЮ ГЛИФОВ
#Создание первых данных
from bokeh.io import output_file
from bokeh.plotting import figure, show

# Мои данные о координатах x-y
x = [1, 2, 1]
y = [1, 1, 2]


output_file('Рисование_данных_с_помощью_глифов1.html', title='First Glyphs')

# Создайте фигуру без панели инструментов и диапазонов осей
fig = figure(title='My Coordinates',
             plot_height=300, plot_width=300,
             x_range=(0, 3), y_range=(0, 3),
             toolbar_location=None)

# Нарисуйте координаты в виде кругов
fig.circle(x=x, y=y,
           color='green', size=10, alpha=0.5)

# Показать сюжет
show(fig)

Результат:

Как только ваша фигура будет создана, вы сможете увидеть, как ее можно использовать для рисования данных координат x-y с использованием настраиваемых круговых глифов. Помимо этого, глифы можно комбинировать по мере необходимости, чтобы соответствовать вашим потребностям в визуализации. Допустим, я хочу создать визуализацию, которая показывает, сколько слов я написал за день, чтобы сделать этот руководство, с наложенной линией тренда совокупного количества слов:

#Создание вторых данных
import numpy as np
from bokeh.plotting import figure, show

# Мои данные о подсчете слов
day_num = np.linspace(1, 10, 10)
daily_words = [450, 628, 488, 210, 287, 791, 508, 639, 397, 943]
cumulative_words = np.cumsum(daily_words)


output_file('Рисование_данных_с_помощью_глифов2.html', title='First Glyphs')

# Создаем фигуру с осью x типа datetime
fig = figure(title='My Tutorial Progress',
             plot_height=400, plot_width=700,
             x_axis_label='Day Number', y_axis_label='Words Written',
             x_minor_ticks=2, y_range=(0, 6000),
             toolbar_location=None)

# Ежедневные слова будут представлены в виде вертикальных полос (столбцов)
fig.vbar(x=day_num, bottom=0, top=daily_words,
         color='blue', width=0.75,
         legend='Daily')

# Накопленная сумма будет линией тренда
fig.line(x=day_num, y=cumulative_words,
         color='gray', line_width=1,
         legend='Cumulative')

# Поместите легенду в левый верхний угол
fig.legend.location = 'top_left'

# Давайте проверим
show(fig)

Результат:

Чтобы объединить столбцы и линии на рисунке, они просто создаются с использованием одного и того же объекта figure (). Кроме того, вы можете увидеть выше, как легко можно создать легенду, задав свойство легенды для каждого глифа. Затем легенда была перемещена в верхний левый угол графика путем присвоения ‘top_left’ значению fig.legend.location.

Небольшая заметка о данных

Каждый раз, когда вы изучаете новую библиотеку визуализации, рекомендуется начинать с некоторых данных из знакомой вам области. Поэтому, в остальных примерах будут использоваться общедоступные данные из Kaggle, который содержит информацию о сезоне 2017-18 Национальной баскетбольной ассоциации (НБА), а именно:
•2017-18_playerBoxScore.csv: снимки статистики игроков по играм;
•2017-18_teamBoxScore.csv: снимки статистики команд по играм;
•2017-18_standings.csv: ежедневное командное положение и рейтинги.
Эти файлы я, соответственно, скачал и разместил в папку с проектом. После чего, они были прочитаны с помощью Pandas DataFrame, используя следующие команды:

import pandas as pd
# Прочитать csv файлы
player_stats = pd.read_csv('2017-18_playerBoxScore.csv', parse_dates=['gmDate'])
team_stats = pd.read_csv('2017-18_teamBoxScore.csv', parse_dates=['gmDate'])
standings = pd.read_csv('2017-18_standings.csv', parse_dates=['stDate'])
print(player_stats)

Этот фрагмент кода считывает данные из трех файлов CSV и автоматически интерпретирует столбцы даты как объекты datetime. Ну и print был применен для того, чтобы проверить, что он выведет, да и считывает ли данные в целом или нет. Результат:

Использование объекта ColumnDataSource

В приведенных выше примерах для представления данных использовались списки Python и массивы Numpy и Bokeh хорошо оснащено для обработки этих типов данных. Однако, когда дело доходит до данных в Python, вы, скорее всего, встретите словари Python и Pandas DataFrames, особенно если вы читаете данные из файла или внешнего источника данных. Bokeh хорошо оснащен для работы с этими более сложными структурами данных и даже имеет встроенные функции для их обработки, а именно ColumnDataSource. Вы можете спросить себя: «Зачем использовать ColumnDataSource, если Bokeh может напрямую взаимодействовать с другими типами данных?» Во-первых, ссылаетесь ли вы напрямую на список, массив, словарь или DataFrame, Bokeh за кулисами все равно превратит его в ColumnDataSource. Что еще более важно, ColumnDataSource значительно упрощает реализацию интерактивных возможностей Bokeh. ColumnDataSource лежит в основе передачи данных глифам, которые вы используете для визуализации.Его основная функция — отображать имена столбцам ваших данных. Это упрощает использование ссылок на элементы данных при построении визуализации. Это также упрощает то же самое для Bokeh при создании визуализации. Давайте начнем с визуализации гонки за первое место в Западной конференции НБА в 2017–2018 годах между действующим чемпионом Golden State Warriors и претендентом Houston Rockets. Ежедневные записи о победах и поражениях этих двух команд хранятся в DataFrame с именем west_top_2:

#ИСПОЛЬЗОВАНИЕ ОБЪЕКТА ColumnDataSource
#Пример 1
west_top_2 = (standings[(standings['teamAbbr'] == 'HOU') | (standings['teamAbbr'] == 'GS')]
              .loc[:, ['stDate', 'teamAbbr', 'gameWon']]
             .sort_values(['teamAbbr','stDate']))
print(west_top_2.head())

Результат:

Отсюда вы можете загрузить этот DataFrame в два объекта ColumnDataSource и визуализировать гонку:

from bokeh.plotting import figure, show
from bokeh.io import output_file
from bokeh.models import ColumnDataSource

# Output to file
output_file('west-top-2-standings-race1.html',
            title='Western Conference Top 2 Teams Wins Race')

# Isolate the data for the Rockets and Warriors
rockets_data = west_top_2[west_top_2['teamAbbr'] == 'HOU']
warriors_data = west_top_2[west_top_2['teamAbbr'] == 'GS']

# Create a ColumnDataSource object for each team
rockets_cds = ColumnDataSource(rockets_data)
warriors_cds = ColumnDataSource(warriors_data)

# Create and configure the figure
fig = figure(x_axis_type='datetime',
             plot_height=300, plot_width=600,
             title='Western Conference Top 2 Teams Wins Race, 2017-18',
             x_axis_label='Date', y_axis_label='Wins',
             toolbar_location=None)

# Render the race as step lines
fig.step('stDate', 'gameWon',
         color='#CE1141', legend='Rockets',
         source=rockets_cds)
fig.step('stDate', 'gameWon',
         color='#006BB6', legend='Warriors',
         source=warriors_cds)

# Move the legend to the upper left corner
fig.legend.location = 'top_left'

# Show the plot
show(fig)

Результат:

Обратите внимание на ссылки на соответствующие объекты ColumnDataSource при создании двух строк. Вы просто передаете исходные имена столбцов в качестве входных параметров и указываете, какой ColumnDataSource использовать через свойство источника. Визуализация показывает напряженную гонку на протяжении всего сезона, Warriors создают довольно большую подушку в середине сезона. Тем не менее, небольшой спад в конце сезона позволил Rockets наверстать упущенное и в конечном итоге превзойти защищающихся чемпионов и завершить сезон посевным номером один в Западной конференции.
В предыдущем примере были созданы два объекта ColumnDataSource, по одному из подмножества фрейма данных west_top_2. В следующем примере будет воссоздан тот же вывод из одного ColumnDataSource на основе всего west_top_2 с использованием GroupFilter, который создает представление для данных:

#Пример 2
from bokeh.plotting import figure, show
from bokeh.io import output_file
from bokeh.models import ColumnDataSource, CDSView, GroupFilter

# Вывод в файл
output_file('west-top-2-standings-race2.html',
            title='Western Conference Top 2 Teams Wins Race')

# Создать ColumnDataSource
west_cds = ColumnDataSource(west_top_2)

# Создайте представления для каждой команды
rockets_view = CDSView(source=west_cds,
                       filters=[GroupFilter(column_name='teamAbbr', group='HOU')])
warriors_view = CDSView(source=west_cds,
                        filters=[GroupFilter(column_name='teamAbbr', group='GS')])

# Создаем и настраиваем фигуру
west_fig = figure(x_axis_type='datetime',
                  plot_height=300, plot_width=600,
                  title='Western Conference Top 2 Teams Wins Race, 2017-18',
                  x_axis_label='Date', y_axis_label='Wins',
                  toolbar_location=None)

# Отрисовываем гонку в виде ступенчатых линий
west_fig.step('stDate', 'gameWon',
              source=west_cds, view=rockets_view,
              color='#CE1141', legend='Rockets')
west_fig.step('stDate', 'gameWon',
              source=west_cds, view=warriors_view,
              color='#006BB6', legend='Warriors')

# Переместите легенду в верхний левый угол
west_fig.legend.location = 'top_left'

# Показать сюжет
show(west_fig)

Результат:

Обратите внимание, как GroupFilter передается в CDSView в виде списка. Это позволяет вам комбинировать несколько фильтров вместе, чтобы при необходимости изолировать нужные данные от ColumnDataSource.

Организация нескольких визуализаций с помощью макетов

В турнирной таблице Восточной конференции в Атлантическом дивизионе остались два соперника: Boston Celtics и Toronto Raptors. Прежде чем повторить шаги, использованные для создания west_top_2, давайте попробуем еще раз протестировать ColumnDataSource, используя то, что вы узнали выше. В этом примере вы увидите, как передать весь DataFrame в ColumnDataSource и создать представления для изоляции соответствующих данных:

#ОРГАНИЗАЦИЯ НЕСКОЛЬКИХ ВИЗУАЛИЗАЦИЙ С ПОМОЩЬЮ МАКЕТОВ
from bokeh.plotting import figure, show
from bokeh.io import output_file
from bokeh.models import ColumnDataSource, CDSView, GroupFilter

# Вывод в файл
output_file('Организация_нескольких_визуализаций_с_помощью_макетов.html',
            title='Eastern Conference Top 2 Teams Wins Race')

# Создать ColumnDataSource
standings_cds = ColumnDataSource(standings)

# Create views for each team
celtics_view = CDSView(source=standings_cds,
                      filters=[GroupFilter(column_name='teamAbbr',
                                           group='BOS')])
raptors_view = CDSView(source=standings_cds,
                      filters=[GroupFilter(column_name='teamAbbr',
                                           group='TOR')])

# Создаем и настраиваем фигуру
east_fig = figure(x_axis_type='datetime',
           plot_height=300, plot_width=600,
           title='Eastern Conference Top 2 Teams Wins Race, 2017-18',
           x_axis_label='Date', y_axis_label='Wins',
           toolbar_location=None)

# Отрисовываем гонку в виде ступенчатых линий
east_fig.step('stDate', 'gameWon',
              color='#007A33', legend='Celtics',
              source=standings_cds, view=celtics_view)
east_fig.step('stDate', 'gameWon',
              color='#CE1141', legend='Raptors',
              source=standings_cds, view=raptors_view)

# Переместите легенду в верхний левый угол
east_fig.legend.location = 'top_left'

# Показать сюжет
show(east_fig)

Результат:

Добавление взаимодействия

Выбор точек данных

Реализовать поведение выбора так же просто, как добавить несколько конкретных ключевых слов при объявлении ваших глифов. В следующем примере создается диаграмма разброса, которая связывает общее количество попыток трехочковых бросков игроком с их процентом (для игроков, сделавших не менее 100 трехочковых бросков). Данные могут быть агрегированы из DataFrame player_stats:

#ДОБАВЛЕНИЕ ВЗАИМОДЕЙСТВИЯ
#ВЫБОР ТОЧЕК ДАННЫХ
# Найдите игроков, которые сделали хотя бы 1 трехочковый бросок за сезон
three_takers = player_stats[player_stats['play3PA'] > 0]

# Убрать имена игроков, поместив их в один столбец
three_takers['name'] = [f'{p["playFNm"]} {p["playLNm"]}'
                        for _, p in three_takers.iterrows()]

# Суммируйте общее количество трехочковых попыток и количество попыток для каждого игрока
three_takers = (three_takers.groupby('name')
                            .sum()
                            .loc[:,['play3PA', 'play3PM']]
                            .sort_values('play3PA', ascending=False))

# Отфильтровать всех, кто не сделал хотя бы 100 трехочковых бросков
three_takers = three_takers[three_takers['play3PA'] >= 100].reset_index()

# Добавить столбец с рассчитанным трехбалльным процентом (выполнено / предпринято)
three_takers['pct3PM'] = three_takers['play3PM'] / three_takers['play3PA']
print(three_takers.sample(5))

Результат:

Допустим, вы хотите выбрать группы игроков в раздаче и при этом отключить цвет глифов, представляющих невыбранных игроков:

# Выбор группы игроков в раздаче из полученного DataFrame, при этом отключить цвет глифов
from bokeh.plotting import figure, show
from bokeh.io import output_file
from bokeh.models import ColumnDataSource, NumeralTickFormatter

# Файл вывода
output_file('Выбор_точек_данных.html',
            title='Three-Point Attempts vs. Percentage')

# Сохраним данные в ColumnDataSource
three_takers_cds = ColumnDataSource(three_takers)

# Укажите инструменты выбора, которые будут доступны
select_tools = ['box_select', 'lasso_select', 'poly_select', 'tap', 'reset']

# Создание картинки
fig = figure(plot_height=400,
             plot_width=600,
             x_axis_label='Three-Point Shots Attempted',
             y_axis_label='Percentage Made',
             title='3PT Shots Attempted vs. Percentage Made (min. 100 3PA), 2017-18',
             toolbar_location='below',
             tools=select_tools)

# Отформатируйте метки делений оси Y в процентах
fig.yaxis[0].formatter = NumeralTickFormatter(format='00.0%')

# Добавить квадрат, представляющий каждого игрока
fig.square(x='play3PA',
           y='pct3PM',
           source=three_takers_cds,
           color='royalblue',
           selection_color='deepskyblue',
           nonselection_color='lightgray',
           nonselection_alpha=0.3)

# Показать результат
show(fig)

Сначала укажите инструменты выбора, которые вы хотите сделать доступными. В приведенном выше примере «box_select», «lasso_select», «poly_select» и «tap» (плюс кнопка сброса) были указаны в списке под названием select_tools. Когда фигура создается, панель инструментов располагается «below» («ниже») графика, и список передается в инструменты, чтобы сделать инструменты, выбранные выше, доступными. Каждый игрок изначально представлен синим квадратным глифом, но при выборе игрока или группы игроков устанавливаются следующие конфигурации:
• Превратите выбранных игроков в темно-синий
• Измените глифы всех невыделенных игроков на светло-серый цвет с непрозрачностью 0,3
Результат:

Добавление действий при наведении курсора

Таким образом, реализована возможность выбора конкретных точек данных игроков, которые кажутся интересными на моем графике разброса, но что, если вы хотите быстро увидеть, каких отдельных игроков представляет глиф? Один из вариантов — использовать Bokeh HoverTool (), чтобы показывать всплывающую подсказку, когда курсор пересекает пути с глифом. Все, что вам нужно сделать, это добавить в приведенный выше фрагмент кода следующее:

#ДОБАВЛЕНИЕ ДЕЙСТВИЙ ПРИ НАВЕДЕНИИ КУРСОРА
from bokeh.models import HoverTool

output_file('Добавление_действий_при_наведении_курсора.html',
            title='Three-Point Attempts vs. Percentage')

# Отформатируйте всплывающую подсказку
# Отформатируйте всплывающую подсказку
tooltips = [
            ('Player','@name'),
            ('Three-Pointers Made', '@play3PM'),
            ('Three-Pointers Attempted', '@play3PA'),
            ('Three-Point Percentage','@pct3PM{00.0%}'),
           ]

# Настроить рендерер, который будет использоваться при наведении
hover_glyph = fig.circle(x='play3PA', y='pct3PM', source=three_takers_cds,
                         size=15, alpha=0,
                         hover_fill_color='black', hover_alpha=0.5)

# Добавляем HoverTool к фигуре
fig.add_tools(HoverTool(tooltips=tooltips, renderers=[hover_glyph]))

# Показать
show(fig)

HoverTool() немного отличается от инструментов выбора, которые вы видели выше, тем, что у него есть свойства, в частности всплывающие подсказки. Во-первых, вы можете настроить отформатированную всплывающую подсказку, создав список кортежей, содержащих описание и ссылку на ColumnDataSource. Этот список был передан в качестве входных данных в HoverTool(), а затем просто добавлен к рисунку с помощью add_tools(). Для большего подчеркивания игроков при наведении, был создан новый глиф, в данном случае кружков вместо квадратов, и присвоения ему hover_glyph. Обратите внимание, что начальная непрозрачность установлена на ноль, поэтому она невидима, пока курсор не коснется ее. Свойства, которые появляются при наведении курсора, фиксируются путем установки hover_alpha значения 0,5 вместе с hover_fill_color. Теперь вы увидите, как маленький черный кружок появляется над исходным квадратом при наведении курсора на различные маркеры. Вот что произошло:

Выделение данных с помощью легенды

В этом примере вы увидите две идентичные диаграммы разброса, на которых сравниваются игровые очки и подборы Леброна Джеймса и Кевина Дюранта. Единственная разница будет заключаться в том, что один будет использовать hide в качестве своей click_policy, а другой — mute. Первый шаг — настроить вывод и настроить данные, создав представление для каждого игрока из кадра данных player_stats:

#ВЫДЕЛЕНИЕ ДАННЫХ С ПОМОЩЬЮ ЛЕГЕНДЫ
from bokeh.plotting import figure, show
from bokeh.io import output_file
from bokeh.models import ColumnDataSource, CDSView, GroupFilter
from bokeh.layouts import row

# Вывести в записной книжке
output_file('lebron-vs-durant.html',
            title='LeBron James vs. Kevin Durant')

# Хранить данные в ColumnDataSource
player_gm_stats = ColumnDataSource(player_stats)

# Создайте представление для каждого игрока
lebron_filters = [GroupFilter(column_name='playFNm', group='LeBron'),
                  GroupFilter(column_name='playLNm', group='James')]
lebron_view = CDSView(source=player_gm_stats,
                      filters=lebron_filters)

durant_filters = [GroupFilter(column_name='playFNm', group='Kevin'),
                  GroupFilter(column_name='playLNm', group='Durant')]
durant_view = CDSView(source=player_gm_stats,
                      filters=durant_filters)

Перед созданием рисунков общие параметры рисунка, маркеры и данные могут быть объединены в словари и повторно использованы. Это не только сохраняет избыточность на следующем шаге, но и предоставляет простой способ настроить эти параметры позже, если потребуется:

# Объединить общие аргументы ключевых слов в dicts
common_figure_kwargs = {
    'plot_width': 400,
    'x_axis_label': 'Points',
    'toolbar_location': None,
}
common_circle_kwargs = {
    'x': 'playPTS',
    'y': 'playTRB',
    'source': player_gm_stats,
    'size': 12,
    'alpha': 0.7,
}
common_lebron_kwargs = {
    'view': lebron_view,
    'color': '#002859',
    'legend': 'LeBron James'
}
common_durant_kwargs = {
    'view': durant_view,
    'color': '#FFC324',
    'legend': 'Kevin Durant'
}

Теперь, когда различные свойства установлены, два графика разброса можно построить гораздо более кратко:

# Создайте две фигуры и нарисуйте данные
hide_fig = figure(**common_figure_kwargs,
                  title='Click Legend to HIDE Data',
                  y_axis_label='Rebounds')
hide_fig.circle(**common_circle_kwargs, **common_lebron_kwargs)
hide_fig.circle(**common_circle_kwargs, **common_durant_kwargs)

mute_fig = figure(**common_figure_kwargs, title='Click Legend to MUTE Data')
mute_fig.circle(**common_circle_kwargs, **common_lebron_kwargs,
                muted_alpha=0.1)
mute_fig.circle(**common_circle_kwargs, **common_durant_kwargs,
                muted_alpha=0.1)

Обратите внимание, что у mute_fig есть дополнительный параметр под названием muted_alpha. Этот параметр управляет непрозрачностью маркеров, когда отключение звука используется как click_policy. Наконец, устанавливается click_policy для каждой фигуры, и они отображаются в горизонтальной конфигурации:

# Добавляем интерактивности в легенду
hide_fig.legend.click_policy = 'hide'
mute_fig.legend.click_policy = 'mute'

# Визуализировать
show(row(hide_fig, mute_fig))

Результат:

После того, как легенда размещена, все, что вам нужно сделать, это назначить либо скрыть, либо отключить для свойства фигуры click_policy. Это автоматически превратит вашу базовую легенду в интерактивную легенду. Также обратите внимание, что специально для отключения звука дополнительное свойство muted_alpha было установлено в соответствующих круговых глифах для Леброна Джеймса и Кевина Дюранта. Это определяет визуальный эффект, обусловленный взаимодействием легенды.