{
    "version": "https:\/\/jsonfeed.org\/version\/1",
    "title": "LEFT JOIN: blog on analytics, visualisation & data science, posts tagged: bootstrap",
    "home_page_url": "https:\/\/en.leftjoin.ru\/tags\/bootstrap\/",
    "feed_url": "https:\/\/en.leftjoin.ru\/tags\/bootstrap\/json\/",
    "icon": "https:\/\/en.leftjoin.ru\/user\/userpic@2x.jpg",
    "author": {
        "name": "Nikolay Valiotti",
        "url": "https:\/\/en.leftjoin.ru\/",
        "avatar": "https:\/\/en.leftjoin.ru\/user\/userpic@2x.jpg"
    },
    "items": [
        {
            "id": "47",
            "url": "https:\/\/en.leftjoin.ru\/all\/how-to-build-a-dashboard-with-bootstrap-4-from-scratch-part-2\/",
            "title": "How to build a dashboard with Bootstrap 4 from scratch (Part 2)",
            "content_html": "<div class=\"e2-text-picture\">\n<img src=\"https:\/\/en.leftjoin.ru\/pictures\/1-18.png\" width=\"2000\" height=\"1154\" alt=\"\" \/>\n<\/div>\n<p>Previously we shared <a href=\"https:\/\/en.leftjoin.ru\/all\/how-to-build-dashboard-with-bootstrap-4-from-scratch-part-1\/\">how to use Bootstrap components in building dashboard layout<\/a> and designed a simple yet flexible dashboard with a scatter plot and Russian map. In today’s material, we will continue adding more information, explore how to make Bootstrap tables responsive, and cover some complex callbacks for data acquisition.<\/p>\n<h2>Constructing Data Tables<\/h2>\n<p>All the code for populating our tables with data will be stored in <span class=\"inline-code\">get_tables.py<\/span> , while the layout components areoutlined in <span class=\"inline-code\"> application.py<\/span>.  This article will cover the process of creating the table with top Russian Breweries,  however, you can find the code for creating the other three on Github.<\/p>\n<p>Data in the Top Breweries table can be filtered by city name in the dropdown menu, but the data collected in Untappd is not equally structured. Some city names are written in Latin, others in Cyrillic. So the challenge is to make the names equal for SQL queries, and here is where Google Translate comes to the rescue. Though we sill have to manually create a dictionary of city names,  since for example “Москва” can be written as “Moskva”  and not “Moscow”.  This dictionary will be used later for mapping our DataFrame before transforming it into a Bootstrap table.<\/p>\n<pre class=\"e2-text-code\"><code>import pandas as pd\r\nimport dash_bootstrap_components as dbc\r\nfrom clickhouse_driver import Client\r\nimport numpy as np\r\nfrom googletrans import Translator\r\n\r\ntranslator = Translator()\r\n\r\nclient = Client(host='12.34.56.78', user='default', password='', port='9000', database='')\r\n\r\ncity_names = {\r\n   'Moskva': 'Москва',\r\n   'Moscow': 'Москва',\r\n   'СПБ': 'Санкт-Петербург',\r\n   'Saint Petersburg': 'Санкт-Петербург',\r\n   'St Petersburg': 'Санкт-Петербург',\r\n   'Nizhnij Novgorod': 'Нижний Новгород',\r\n   'Tula': 'Тула',\r\n   'Nizhniy Novgorod': 'Нижний Новгород',\r\n}<\/code><\/pre><h2>Top Breweries Table<\/h2>\n<p>This table displays top 10 Russian breweries and their position change according to the rating. Simply put, we need to compare data for two periods, that’s [30 days ago; today] and [60 days ago; 30 days ago]. With this in mind, we will need the following headers: ranking, brewery name, position change, and number of check-ins.<br \/>\nCreate the  <span class=\"inline-code\">get_top_russian_breweries<\/span> function that would make queries to the Clickhouse DB, sort the data and return a refined Pandas DataFrame. Let’s send the following queries to obtain data for the past 30 and 60 days, ordering the results by the number of check-ins.<\/p>\n<p><details><br \/>\n<summary><span style=\"color:#7ea9b8\">Querying data from the Database<\/span><\/summary><\/p>\n<pre class=\"e2-text-code\"><code>def get_top_russian_breweries(checkins_n=250):\r\n   top_n_brewery_today = client.execute(f'''\r\n      SELECT  rt.brewery_id,\r\n              rt.brewery_name,\r\n              beer_pure_average_mult_count\/count_for_that_brewery as avg_rating,\r\n              count_for_that_brewery as checkins FROM (\r\n      SELECT           \r\n              brewery_id,\r\n              dictGet('breweries', 'brewery_name', toUInt64(brewery_id)) as brewery_name,\r\n              sum(rating_score) AS beer_pure_average_mult_count,\r\n              count(rating_score) AS count_for_that_brewery\r\n          FROM beer_reviews t1\r\n          ANY LEFT JOIN venues AS t2 ON t1.venue_id = t2.venue_id\r\n          WHERE isNotNull(venue_id) AND (created_at &gt;= (today() - 30)) AND (venue_country = 'Россия') \r\n          GROUP BY           \r\n              brewery_id,\r\n              brewery_name) rt\r\n      WHERE (checkins&gt;={checkins_n})\r\n      ORDER BY avg_rating DESC\r\n      LIMIT 10\r\n      '''\r\n   )\r\n\r\ntop_n_brewery_n_days = client.execute(f'''\r\n  SELECT  rt.brewery_id,\r\n          rt.brewery_name,\r\n          beer_pure_average_mult_count\/count_for_that_brewery as avg_rating,\r\n          count_for_that_brewery as checkins FROM (\r\n  SELECT           \r\n          brewery_id,\r\n          dictGet('breweries', 'brewery_name', toUInt64(brewery_id)) as brewery_name,\r\n          sum(rating_score) AS beer_pure_average_mult_count,\r\n          count(rating_score) AS count_for_that_brewery\r\n      FROM beer_reviews t1\r\n      ANY LEFT JOIN venues AS t2 ON t1.venue_id = t2.venue_id\r\n      WHERE isNotNull(venue_id) AND (created_at &gt;= (today() - 60) AND created_at &lt;= (today() - 30)) AND (venue_country = 'Россия')\r\n      GROUP BY           \r\n          brewery_id,\r\n          brewery_name) rt\r\n  WHERE (checkins&gt;={checkins_n})\r\n  ORDER BY avg_rating DESC\r\n  LIMIT 10\r\n  '''\r\n)<\/code><\/pre><p><\/details><\/p>\n<p>Creating two DataFrames with the received data:<\/p>\n<pre class=\"e2-text-code\"><code>top_n = len(top_n_brewery_today)\r\ncolumn_names = ['brewery_id', 'brewery_name', 'avg_rating', 'checkins']\r\n\r\ntop_n_brewery_today_df = pd.DataFrame(top_n_brewery_today, columns=column_names).replace(np.nan, 0)\r\ntop_n_brewery_today_df['brewery_pure_average'] = round(top_n_brewery_today_df.avg_rating, 2)\r\ntop_n_brewery_today_df['brewery_rank'] = list(range(1, top_n + 1))\r\n\r\ntop_n_brewery_n_days = pd.DataFrame(top_n_brewery_n_days, columns=column_names).replace(np.nan, 0)\r\ntop_n_brewery_n_days['brewery_pure_average'] = round(top_n_brewery_n_days.avg_rating, 2)\r\ntop_n_brewery_n_days['brewery_rank'] = list(range(1, len(top_n_brewery_n_days) + 1))<\/code><\/pre><p>And then calculate the position change over the period of time for each brewery received. With the try-except block, we will handle exceptions, in case, if a brewery was not yet in our database 60 days ago.<\/p>\n<pre class=\"e2-text-code\"><code>rank_was_list = []\r\nfor brewery_id in top_n_brewery_today_df.brewery_id:\r\n   try:\r\n       rank_was_list.append(\r\n           top_n_brewery_n_days[top_n_brewery_n_days.brewery_id == brewery_id].brewery_rank.item())\r\n   except ValueError:\r\n       rank_was_list.append('–')\r\ntop_n_brewery_today_df['rank_was'] = rank_was_list<\/code><\/pre><p>Now we iterate over the columns with current and former positions. If there is no hyphen contained in, we will append an up or down arrow depending on the change.<\/p>\n<pre class=\"e2-text-code\"><code>diff_rank_list = []\r\nfor rank_was, rank_now in zip(top_n_brewery_today_df['rank_was'], top_n_brewery_today_df['brewery_rank']):\r\n   if rank_was != '–':\r\n       difference = rank_was - rank_now\r\n       if difference &gt; 0:\r\n           diff_rank_list.append(f'↑ +{difference}')\r\n       elif difference &lt; 0:\r\n           diff_rank_list.append(f'↓ {difference}')\r\n       else:\r\n           diff_rank_list.append('–')\r\n   else:\r\n       diff_rank_list.append(rank_was)<\/code><\/pre><p>Finally,  replace DataFrame headers, inserting the column with current ranking positions, where the top 3 will be displayed with the trophy emoji.<\/p>\n<pre class=\"e2-text-code\"><code>df = top_n_brewery_today_df[['brewery_name', 'avg_rating', 'checkins']].round(2)\r\ndf.insert(2, 'Position change', diff_rank_list)\r\ndf.columns = ['NAME', 'RATING', 'POSITION CHANGE', 'CHECK-INS']\r\ndf.insert(0, 'RANKING', list('🏆 ' + str(i) if i in [1, 2, 3] else str(i) for i in range(1, len(df) + 1)))\r\n\r\nreturn df<\/code><\/pre><h2>Filtering data by city name<\/h2>\n<p>One of the main tasks we set before creating this dashboard was to find out what are the most liked breweries in a certain city. The user chooses a city in the dropdown menu and gets the results. Sound pretty simple, but is it that easy?<br \/>\nOur next step is to write a script that would update data for each city and store it in separate CSV files. As we mentioned earlier, the city names are not equally structured, so we need to use Google Translator within the if-else block, and since it may not convert some names to Cyrillic we need to explicitly specify such cases:<\/p>\n<pre class=\"e2-text-code\"><code>en_city = venue_city\r\nif en_city == 'Nizhnij Novgorod':\r\n      ru_city = 'Нижний Новгород'\r\nelif en_city == 'Perm':\r\n      ru_city = 'Пермь'\r\nelif en_city == 'Sergiev Posad':\r\n      ru_city = 'Сергиев Посад'\r\nelif en_city == 'Vladimir':\r\n      ru_city = 'Владимир'\r\nelif en_city == 'Yaroslavl':\r\n      ru_city = 'Ярославль'\r\nelse:\r\n      ru_city = translator.translate(en_city, dest='ru').text<\/code><\/pre><p>Then we need to add both city names in English and Russian to the SQL query, to receive all check-ins sent from this city.<\/p>\n<pre class=\"e2-text-code\"><code>WHERE (rt.venue_city='{ru_city}' OR rt.venue_city='{en_city}')<\/code><\/pre><p>Finally, we export received data into a CSV file in the following directory –  <span class=\"inline-code\">data\/cities<\/span>.<\/p>\n<pre class=\"e2-text-code\"><code>df = top_n_brewery_today_df[['brewery_name', 'venue_city', 'avg_rating', 'checkins']].round(2)\r\ndf.insert(3, 'Position Change', diff_rank_list)\r\ndf.columns = ['NAME', 'CITY', 'RATING', 'POSITION CHANGE', 'CHECK-INS']\r\n# MAPPING\r\ndf['CITY'] = df['CITY'].map(lambda x: city_names[x] if (x in city_names) else x)\r\n# TRANSLATING\r\ndf['CITY'] = df['CITY'].map(lambda x: translator.translate(x, dest='en').text)\r\ndf.to_csv(f'data\/cities\/{en_city}.csv', index=False)\r\nprint(f'{en_city}.csv updated!')<\/code><\/pre><h2>Scheduling Updates<\/h2>\n<p>We will use the <span class=\"inline-code\">apscheduler<\/span>  library to automatically run the script and refresh data for each city in <span class=\"inline-code\">all_cities<\/span> every day at 10:30 am (UTC).<\/p>\n<pre class=\"e2-text-code\"><code>from apscheduler.schedulers.background import BackgroundScheduler\r\nfrom get_tables import update_best_breweries\r\n\r\nall_cities = sorted(['Vladimir', 'Voronezh', 'Ekaterinburg', 'Kazan', 'Red Pakhra', 'Krasnodar',\r\n             'Kursk', 'Moscow', 'Nizhnij Novgorod', 'Perm', 'Rostov-on-Don', 'Saint Petersburg',\r\n             'Sergiev Posad', 'Tula', 'Yaroslavl'])\r\n\r\nscheduler = BackgroundScheduler()\r\n@scheduler.scheduled_job('cron', hour=10, misfire_grace_time=30)\r\ndef update_data():\r\n   for city in all_cities:\r\n       update_best_breweries(city)\r\nscheduler.start()<\/code><\/pre><h2>Table from DataFrame<\/h2>\n<p><span class=\"inline-code\">get_top_russian_breweries_table(venue_city, checkins_n=250)<\/span>  will accept venue_city and checkins_n generating a Bootstrap Table with the top breweries. The second parameter value,  <span class=\"inline-code\">checkins_n<\/span>  can be changed with the slider. If the city name is not specified, the function will return top Russian breweries table.<\/p>\n<pre class=\"e2-text-code\"><code>if venue_city == None: \r\n      selected_df = get_top_russian_breweries(checkins_n)\r\nelse: \r\n      en_city = venue_city<\/code><\/pre><p>In other case the DataFrame will be constructed from a CSV file stored in <span class=\"inline-code\">data\/cities\/<\/span>. Since the city column still may contain different names we should apply mapping and use a lambda expression with the <span class=\"inline-code\">map()<\/span> method. The lambda function will compare values in the column against keys in <span class=\"inline-code\">city_names<\/span> and if there is a match, the column value will be overwritten.<br \/>\nFor instance,  if <span class=\"inline-code\">df[‘CITY’]<\/span> contains  “СПБ”, a frequent acronym for Saint Petersburg, the value will be replaced, while for “Воронеж” it will remain unchanged.<br \/>\nAnd last but not least, we need to remove all duplicate rows from the table, add a column with a ranking position and return the first 10 rows. These would be the most liked breweries in a selected city.<\/p>\n<pre class=\"e2-text-code\"><code>df = pd.read_csv(f'data\/cities\/{en_city}.csv')     \r\ndf = df.loc[df['CHECK-INS'] &gt;= checkins_n]\r\ndf.drop_duplicates(subset=['NAME', 'CITY'], keep='first', inplace=True)  \r\ndf.insert(0, 'RANKING', list('🏆 ' + str(i) if i in [1, 2, 3] else str(i) for i in range(1, len(df) + 1)))\r\nselected_df = df.head(10)<\/code><\/pre><p>After all DataFrame manipulations, the function returns a simply styled Bootstrap table of top breweries.<\/p>\n<p><details><br \/>\n<summary><span style=\"color:#7ea9b8\">Bootstrap table layout in DBC<\/span><\/summary><\/p>\n<pre class=\"e2-text-code\"><code>table = dbc.Table.from_dataframe(selected_df, striped=False,\r\n                                bordered=False, hover=True,\r\n                                size='sm',\r\n                                style={'background-color': '#ffffff',\r\n                                       'font-family': 'Proxima Nova Regular',\r\n                                       'text-align':'center',\r\n                                       'fontSize': '12px'},\r\n                                className='table borderless'\r\n                                )\r\n\r\nreturn table<\/code><\/pre><h2>Layout structure<\/h2>\n<p>Add a Slider and a Dropdown menu with city names in <span class=\"inline-code\">application.py<\/span><\/p>\n<p class=\"note\">To learn more about the Dashboard layout structure, please refer to <a href=\"https:\/\/en.leftjoin.ru\/all\/how-to-build-dashboard-with-bootstrap-4-from-scratch-part-1\/\">our previous guide<\/a><\/p>\n<pre class=\"e2-text-code\"><code>checkins_slider_tab_1 = dbc.CardBody(\r\n                           dbc.FormGroup(\r\n                               [\r\n                                   html.H6('Number of check-ins', style={'text-align': 'center'})),\r\n                                   dcc.Slider(\r\n                                       id='checkin_n_tab_1',\r\n                                       min=0,\r\n                                       max=250,\r\n                                       step=25,\r\n                                       value=250,  \r\n                                       loading_state={'is_loading': True},\r\n                                       marks={i: i for i in list(range(0, 251, 25))}\r\n                                   ),\r\n                               ],\r\n                           ),\r\n                           style={'max-height': '80px', \r\n                                  'padding-top': '25px'\r\n                                  }\r\n                       )\r\n\r\ntop_breweries = dbc.Card(\r\n       [\r\n           dbc.CardBody(\r\n               [\r\n                   dbc.FormGroup(\r\n                       [\r\n                           html.H6('Filter by city', style={'text-align': 'center'}),\r\n                           dcc.Dropdown(\r\n                               id='city_menu',\r\n                               options=[{'label': i, 'value': i} for i in all_cities],\r\n                               multi=False,\r\n                               placeholder='Select city',\r\n                               style={'font-family': 'Proxima Nova Regular'}\r\n                           ),\r\n                       ],\r\n                   ),\r\n                   html.P(id=&quot;tab-1-content&quot;, className=&quot;card-text&quot;),\r\n               ],\r\n           ),\r\n   ],\r\n)<\/code><\/pre><p><\/details><\/p>\n<p>We’ll also need to add a callback function to update the table by dropdown menu and slider values:<\/p>\n<pre class=\"e2-text-code\"><code>@app.callback(\r\n   Output(&quot;tab-1-content&quot;, &quot;children&quot;), [Input(&quot;city_menu&quot;, &quot;value&quot;),\r\n                                         Input(&quot;checkin_n_tab_1&quot;, &quot;value&quot;)]\r\n)\r\ndef table_content(city, checkin_n):\r\n   return get_top_russian_breweries_table(city, checkin_n)<\/code><\/pre><p>Tada, the main table is ready! The dashboard can be used to receive up-to-date info about best Russian breweries, beers, and its rating across different regions, and help to make a better choice for an enjoyable tasting experience.<\/p>\n<div class=\"e2-text-picture\">\n<a href=\"http:\/\/dashboard-final-en.us-east-2.elasticbeanstalk.com\/\" class=\"e2-text-picture-link\">\n<img src=\"https:\/\/en.leftjoin.ru\/pictures\/2-17.png\" width=\"1234\" height=\"630\" alt=\"\" \/>\n<\/a><\/div>\n<p><i>View the code on <a href=\"https:\/\/github.com\/valiotti\/leftjoin\/tree\/master\/rutappd\">GitHub<\/a><\/i><\/p>\n",
            "date_published": "2020-10-07T16:35:17+03:00",
            "date_modified": "2020-10-29T09:46:53+03:00",
            "image": "https:\/\/en.leftjoin.ru\/pictures\/1-18.png",
            "_date_published_rfc2822": "Wed, 07 Oct 2020 16:35:17 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "47",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css"
                ],
                "og_images": [
                    "https:\/\/en.leftjoin.ru\/pictures\/1-18.png",
                    "https:\/\/en.leftjoin.ru\/pictures\/2-17.png"
                ]
            }
        },
        {
            "id": "43",
            "url": "https:\/\/en.leftjoin.ru\/all\/how-to-build-dashboard-with-bootstrap-4-from-scratch-part-1\/",
            "title": "How to build a dashboard with Bootstrap 4 from scratch (Part 1)",
            "content_html": "<p>In previous articles we reviewed  Plotly’s Dash Framework,  <a href=\"https:\/\/en.leftjoin.ru\/all\/building-a-scatter-plot-for-untappd-breweries\/\">learned to build scatter plots<\/a> and <a href=\"https:\/\/en.leftjoin.ru\/all\/visualizing-covid-19-in-russia-with-plotly\/\"> create a map visualization<\/a>. This time we will summarize our knowledge and put all the pieces together to design a dashboard layout using the Bootstrap 4 grid system.<br \/>\nTo facilitate the development, we’ll refer to the <a href=\"https:\/\/dash-bootstrap-components.opensource.faculty.ai\/\">dash-bootstrap-components<\/a> library. This is a great tool that integrates Bootstrap in Dash, allowing us to write web pages in pure Python, and add any Bootstrap components and styling.<\/p>\n<h2>Draft Layout<\/h2>\n<p>Before we begin coding it’s crucial to have a plan of our app, a rough layout that would help us to see the big picture and quickly modify the structure.  We used <a href=\"https:\/\/app.diagrams.net\/\">draw.io<\/a> to make a dashboard draft, this application enables to create diagrams, graphs,  flowcharts, and forms at the click of a button. The dashboard will be built according to this template:<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/en.leftjoin.ru\/pictures\/template_1@2x.png\" width=\"872\" height=\"946\" alt=\"\" \/>\n<\/div>\n<p>Like the dashboard itself,  the top header will be colored in gold and white, the main colors of <a href=\"https:\/\/untappd.com\/\">Untappd<\/a>.  Just below the header, there is a section with breweries, which includes a scatter plot and a control panel.  And at the bottom of the page, there will be a map showing beverage rating across the regions of Russia.<\/p>\n<p>All right, let’s get started, first create a new python file with the name application.py. The file will store all the front end components of the dashboard, and create a new directory named assets. The directory structure should be similar:<\/p>\n<pre class=\"e2-text-code\"><code>- application.py\r\n- assets\/\r\n    |-- typography.css\r\n    |-- header.css\r\n    |-- custom-script.js\r\n    |-- image.png<\/code><\/pre><p>Then we import the libraries and initialize our application:<\/p>\n<pre class=\"e2-text-code\"><code>import dash\r\nimport dash_bootstrap_components as dbc\r\nimport dash_html_components as html\r\nimport dash_core_components as dcc\r\nimport pandas as pd\r\nfrom get_ratio_scatter_plot import get_plot\r\nfrom get_russian_map import get_map\r\nfrom clickhouse_driver import Client\r\nfrom dash.dependencies import Input, Output\r\n\r\nstandard_BS = dbc.themes.BOOTSTRAP\r\napp = dash.Dash(__name__, external_stylesheets=[standard_BS])<\/code><\/pre><p>Main parameters of the app:<br \/>\n<span class=\"inline-code\">__name__<\/span>  — to enable access to static elements stored in the assets folder (such as images, CSS and JS files)<br \/>\n<span class=\"inline-code\">external_stylesheets<\/span> — external CSS styling,  here we are using a standard Bootstrap theme, however you can create your own theme  or use any of <a href=\"https:\/\/www.bootstrapcdn.com\/bootswatch\/\"> the availables ones<\/a>.<\/p>\n<p>Hook up a few more things to work with local files  and connect to the Clickhouse Database:<\/p>\n<pre class=\"e2-text-code\"><code>app.scripts.config.serve_locally = True\r\napp.css.config.serve_locally = True\r\n\r\nclient = Client(host='ec2-3-16-148-63.us-east-2.compute.amazonaws.com',\r\n                user='default',\r\n                password='',\r\n                port='9000',\r\n                database='default')<\/code><\/pre><p>Add a palette of colors:<\/p>\n<pre class=\"e2-text-code\"><code>colors = ['#ffcc00', \r\n          '#f5f2e8', \r\n          '#f8f3e3',\r\n          '#ffffff', \r\n          ]<\/code><\/pre><h2>Creating a layout<\/h2>\n<p>All the dashboard elements will be placed within a Bootstrap container,  which is in the  &lt;div&gt block:<\/p>\n<pre class=\"e2-text-code\"><code>- app \r\n    |-- div\r\n     |-- container\r\n      |-- logo&amp;header\r\n     |-- container\r\n      |-- div\r\n       |-- controls&amp;scatter\r\n       |-- map<\/code><\/pre><pre class=\"e2-text-code\"><code>app.layout = html.Div(\r\n                    [\r\n                        dbc.Container(\r\n\r\n                                         &lt; header&gt;\r\n                         \r\n                        dbc.Container(       \r\n                            html.Div(\r\n                                [\r\n                        \r\n                                    &lt; body &gt;\r\n                        \r\n                                ],\r\n                            ),\r\n                            fluid=False, style={'max-width': '1300px'},\r\n                        ),\r\n                    ],\r\n                    style={'background-color': colors[1], 'font-family': 'Proxima Nova Bold'},\r\n                )<\/code><\/pre><p>Here we set a fixed container width, background color, and font style of the page that is stored in typography.css in the assets folder. Let’s take a closer look at the first element in the div block,  that’s the top header with the Untappd logo:<\/p>\n<pre class=\"e2-text-code\"><code>logo = html.Img(src=app.get_asset_url('logo.png'),\r\n                        style={'width': &quot;128px&quot;, 'height': &quot;128px&quot;,\r\n                        }, className='inline-image')<\/code><\/pre><p>and the header:<\/p>\n<pre class=\"e2-text-code\"><code>header = html.H3(&quot;Russian breweries stats from Untappd&quot;, style={'text-transform': &quot;uppercase&quot;})<\/code><\/pre><p>We used Bootstrap Forms to position these two elements on the same level.<\/p>\n<pre class=\"e2-text-code\"><code>logo_and_header = dbc.FormGroup(\r\n        [\r\n            logo,\r\n            html.Div(\r\n                [\r\n                    header\r\n                ],\r\n                className=&quot;p-5&quot;\r\n            )\r\n        ],\r\n        className='form-row',\r\n)<\/code><\/pre><p>The class name  ‘p-5’ allows to increase padding and vertically align the title while specifying ‘form-row’  as the form class name we put the logo and header in one row.  At this point, the top header should  look the following:<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/en.leftjoin.ru\/pictures\/logo_and_header.png\" width=\"2132\" height=\"242\" alt=\"\" \/>\n<\/div>\n<p>Now we need to center the elements and add some colors.  Create a separate container that will take one row. Specify <span class=\"inline-code\">‘d-flex justify-content-center’<\/span> in the <span class=\"inline-code\">className<\/span>  to achieve the same output.<\/p>\n<pre class=\"e2-text-code\"><code>dbc.Container(\r\n                    dbc.Row(\r\n                        [\r\n                            dbc.Col(\r\n                                html.Div(\r\n                                    logo_and_header,\r\n                                ),\r\n                            ),\r\n                        ],\r\n                        style={'max-height': '128px',\r\n                               'color': 'white',\r\n                       }\r\n\r\n                    ),\r\n                    className='d-flex justify-content-center',\r\n                    style={'max-width': '100%',\r\n                           'background-color': colors[0]},\r\n                ),<\/code><\/pre><p>And now the top header is done:<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/en.leftjoin.ru\/pictures\/top-header.png\" width=\"2200\" height=\"245\" alt=\"\" \/>\n<\/div>\n<p>We’re approaching the main part, create the next Bootstrap Container and add a subheading:<\/p>\n<pre class=\"e2-text-code\"><code>dbc.Container(\r\n                    html.Div(\r\n                        [\r\n                            html.Br(),\r\n                            html.H5(&quot;Breweries&quot;, style={'text-align':'center', 'text-transform': 'uppercase'}),\r\n                            html.Hr(), # horizontal  break<\/code><\/pre><p>The main body will consist of Bootstrap Cards, they can provide a structured layout of all parts,  giving each element a clear border and saving the white space. Create the next element, a control panel with sliders:<\/p>\n<pre class=\"e2-text-code\"><code>slider_day_values = [1, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]\r\nslider_top_breweries_values = [5, 25, 50, 75, 100, 125, 150, 175, 200]\r\n\r\ncontrols = dbc.Card(\r\n    [\r\n       dbc.CardBody(\r\n           [\r\n               dbc.FormGroup(\r\n                    [\r\n                        dbc.Label(&quot;Time Period&quot;, style={'text-align': 'center', 'font-size': '100%', 'text-transform': 'uppercase'}),\r\n                        dcc.Slider(\r\n                            id='slider-day',\r\n                            min=1,\r\n                            max=100,\r\n                            step=10,\r\n                            value=100,\r\n                            marks={i: i for i in slider_day_values}\r\n                        ),\r\n                    ], style={'text-align': 'center'}\r\n               ),\r\n               dbc.FormGroup(\r\n                    [\r\n                        dbc.Label(&quot;Number of breweries&quot;, style={'text-align': 'center', 'font-size': '100%', 'text-transform': 'uppercase'}),\r\n                        dcc.Slider(\r\n                            id='slider-top-breweries',\r\n                            min=5,\r\n                            max=200,\r\n                            step=5,\r\n                            value=200,\r\n                            marks={i: i for i in slider_top_breweries_values}\r\n                        ),\r\n                    ], style={'text-align': 'center'}\r\n               ),\r\n           ],\r\n       )\r\n    ],\r\n    style={'height': '32.7rem', 'background-color': colors[3]}\r\n)<\/code><\/pre><div class=\"e2-text-picture\">\n<img src=\"https:\/\/en.leftjoin.ru\/pictures\/2@2x.png\" width=\"291.5\" height=\"149\" alt=\"\" \/>\n<\/div>\n<p>The control panel consists of two sliders that can be used to change the view on the scatter, they are positioned one below the other in a Bootstrap Form. The sliders were put inside the dbc.CardBody block, other elements will be added in the same way. It allows to eliminate alignment problem and achieve clear borders.  By default, the sliders are painted in blue, but we can easily customize them by changing the properties of the class in sliders.css.  Add the control panel with the scatter plot as follows:<\/p>\n<pre class=\"e2-text-code\"><code>dbc.Row(\r\n                [\r\n                    dbc.Col(controls, width={&quot;size&quot;: 4,\r\n                                     &quot;order&quot;: 'first',\r\n                                             &quot;offset&quot;: 0},\r\n                     ),\r\n                     dbc.Col(dbc.Card(\r\n                                [\r\n                                    dbc.CardBody(\r\n                                        [\r\n                                            html.H6(&quot;The ratio between the number of reviews and the average brewery rating&quot;,\r\n                                                    className=&quot;card-title&quot;,\r\n                                                    style={'text-transform': 'uppercase'}), \r\n                                            dcc.Graph(id='ratio-scatter-plot'),\r\n                                        ],\r\n                                    ),\r\n                                ],\r\n                                style={'background-color': colors[2], 'text-align':'center'}\r\n                             ),\r\n                     md=8),\r\n                ],\r\n                align=&quot;start&quot;,\r\n                justify='center',\r\n            ),\r\nhtml.Br(),<\/code><\/pre><p>And at the bottom of the page we will position the map:<\/p>\n<pre class=\"e2-text-code\"><code>html.H5(&quot;Venues and Regions&quot;, style={'text-align':'center', 'text-transform': 'uppercase',}),\r\n                            html.Hr(), # horizontal  break\r\n                            dbc.Row(\r\n                                [\r\n                                    dbc.Col(\r\n                                        dbc.Card(\r\n                                            [\r\n                                                dbc.CardBody(\r\n                                                    [\r\n                                                        html.H6(&quot;Average beer rating across regions&quot;,\r\n                                                                className=&quot;card-title&quot;,\r\n                                                                style={'text-transform': 'uppercase'},\r\n                                                        ),  \r\n                                                        dcc.Graph(figure=get_map())\r\n                                                    ],\r\n                                                ),\r\n                                            ],\r\n                                        style={'background-color': colors[2], 'text-align': 'center'}\r\n                                        ),\r\n                                md=12),\r\n                                ]\r\n                            ),\r\n                            html.Br(),<\/code><\/pre><h2>Callbacks in Dash<\/h2>\n<p>Callback functions allow making dashboard elements interactive through the  Input and Output properties of a particular component.<\/p>\n<pre class=\"e2-text-code\"><code>@app.callback(\r\n    Output('ratio-scatter-plot', 'figure'),\r\n    [Input('slider-day', 'value'),\r\n     Input('slider-top-breweries', 'value'),\r\n     ]\r\n)\r\ndef get_scatter_plots(n_days=100, top_n=200):\r\n    if n_days == 100 and top_n == 200:\r\n        df = pd.read_csv('data\/ratio_scatter_plot.csv')\r\n        return get_plot(n_days, top_n, df)\r\n    else:\r\n        return get_plot(n_days, top_n)<\/code><\/pre><p>In this example, our inputs are the “value” properties of the components that have the ids “slider-day’” and  “slider-top-breweries”. Our output is the “children” property of the component with the id “ratio-scatter-plot”. When the input values are changed, the decorator function will be called automatically and the output on the scatter is updated. Learn more about callbacks from <a href=\"https:\/\/dash.plotly.com\/basic-callbacks\/\">the examples in the docs.<\/a><br \/>\nIt’s worth noting, that the scatter plot may not be displayed correctly when the page is loaded. To avoid this scenario we need to specify its initial state and produce a scatter plot from the saved CSV file stored in the data folder.  Then, when changing the slider values, all data will be taken directly from the Clickhouse tables.<\/p>\n<div class=\"e2-text-picture\">\n<div class=\"fotorama\" data-width=\"833\" data-ratio=\"1.595785440613\">\n<img src=\"https:\/\/en.leftjoin.ru\/pictures\/scatter_empty_2@2x.png\" width=\"833\" height=\"522\" alt=\"\" \/>\n<img src=\"https:\/\/en.leftjoin.ru\/pictures\/scatter_2@2x.png\" width=\"828\" height=\"515\" alt=\"\" \/>\n<\/div>\n<\/div>\n<p>Add a few more lines responsible for deployment and our app is ready to run:<\/p>\n<pre class=\"e2-text-code\"><code>application = app.server\r\n\r\nif __name__ == '__main__':\r\n    application.run(debug=True, port=8000)<\/code><\/pre><p>Next, we need to  <a href=\"https:\/\/en.leftjoin.ru\/all\/deploying-analytical-web-app-with-aws-elastic-beanstalk\/\">deploy our app to AWS BeansTalk<\/a> and <a href=\"http:\/\/unappd-part-1-en.us-east-2.elasticbeanstalk.com\/\">the first part of our Bootstrap Dashboard is completed<\/a>:<\/p>\n<div class=\"embed-responsive embed-responsive-4by3\" style=\"min-width:500\"><p><iframe id=\"igraph\" scrolling=\"yes\" style=\"border:none;\"seamless=\"seamless\" src='http:\/\/unappd-part-1-en.us-east-2.elasticbeanstalk.com\/' height=\"1360px\" width=\"1100px\"<\/p>\n<\/iframe><\/div><p>Thanks for reading the first part of our series about Bootstrap Dashboards, in the next one we are going to add more new components, improved callbacks, and talk about tables in Bootstrap.<\/p>\n<p><a href=\"https:\/\/github.com\/valiotti\/leftjoin\/tree\/master\/untappd_dashboard_en%20(part_1)\/\"> View the code on Github<\/a><\/p>\n",
            "date_published": "2020-09-16T16:21:28+03:00",
            "date_modified": "2020-10-29T10:04:36+03:00",
            "image": "https:\/\/en.leftjoin.ru\/pictures\/template_1.png",
            "_date_published_rfc2822": "Wed, 16 Sep 2020 16:21:28 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "43",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/fotorama\/fotorama.css",
                    "system\/library\/fotorama\/fotorama.js",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css"
                ],
                "og_images": [
                    "https:\/\/en.leftjoin.ru\/pictures\/template_1.png",
                    "https:\/\/en.leftjoin.ru\/pictures\/template_1@2x.png",
                    "https:\/\/en.leftjoin.ru\/pictures\/logo_and_header.png",
                    "https:\/\/en.leftjoin.ru\/pictures\/top-header.png",
                    "https:\/\/en.leftjoin.ru\/pictures\/2@2x.png",
                    "https:\/\/en.leftjoin.ru\/pictures\/scatter_empty_2@2x.png",
                    "https:\/\/en.leftjoin.ru\/pictures\/scatter_2@2x.png"
                ]
            }
        }
    ],
    "_e2_version": 3386,
    "_e2_ua_string": "E2 (v3386; Aegea)"
}