├── datasets ├── operation.db ├── data_warehouse.db ├── sources │ ├── Sales Orders.xls │ └── Product Details.xls ├── __pycache__ │ ├── elt.cpython-38.pyc │ ├── extract_load.cpython-38.pyc │ ├── initialize_datawh.cpython-38.pyc │ └── operation_database.cpython-38.pyc ├── extract_load.py ├── initialize_datawh.py ├── elt.py ├── operation_database.py └── DBmanager.py ├── assets ├── images │ ├── cmd_1.PNG │ ├── cmd_2.PNG │ ├── cmd_3.PNG │ ├── cmd_4.PNG │ ├── logo.png │ ├── DATA FLOW.png │ ├── dashboard.PNG │ ├── KPI_indicator.png │ ├── dashboard_graph.png │ ├── dashboard_show.png │ ├── dashboard_graph1.png │ ├── dashboard_graph2.png │ └── Product details and Sale Order Details.png └── index.css ├── LICENSE ├── README.md └── app.py /datasets/operation.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thongekchakrit/dashboard_dash_mock_sales/HEAD/datasets/operation.db -------------------------------------------------------------------------------- /assets/images/cmd_1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thongekchakrit/dashboard_dash_mock_sales/HEAD/assets/images/cmd_1.PNG -------------------------------------------------------------------------------- /assets/images/cmd_2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thongekchakrit/dashboard_dash_mock_sales/HEAD/assets/images/cmd_2.PNG -------------------------------------------------------------------------------- /assets/images/cmd_3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thongekchakrit/dashboard_dash_mock_sales/HEAD/assets/images/cmd_3.PNG -------------------------------------------------------------------------------- /assets/images/cmd_4.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thongekchakrit/dashboard_dash_mock_sales/HEAD/assets/images/cmd_4.PNG -------------------------------------------------------------------------------- /assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thongekchakrit/dashboard_dash_mock_sales/HEAD/assets/images/logo.png -------------------------------------------------------------------------------- /datasets/data_warehouse.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thongekchakrit/dashboard_dash_mock_sales/HEAD/datasets/data_warehouse.db -------------------------------------------------------------------------------- /assets/images/DATA FLOW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thongekchakrit/dashboard_dash_mock_sales/HEAD/assets/images/DATA FLOW.png -------------------------------------------------------------------------------- /assets/images/dashboard.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thongekchakrit/dashboard_dash_mock_sales/HEAD/assets/images/dashboard.PNG -------------------------------------------------------------------------------- /assets/images/KPI_indicator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thongekchakrit/dashboard_dash_mock_sales/HEAD/assets/images/KPI_indicator.png -------------------------------------------------------------------------------- /assets/images/dashboard_graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thongekchakrit/dashboard_dash_mock_sales/HEAD/assets/images/dashboard_graph.png -------------------------------------------------------------------------------- /assets/images/dashboard_show.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thongekchakrit/dashboard_dash_mock_sales/HEAD/assets/images/dashboard_show.png -------------------------------------------------------------------------------- /datasets/sources/Sales Orders.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thongekchakrit/dashboard_dash_mock_sales/HEAD/datasets/sources/Sales Orders.xls -------------------------------------------------------------------------------- /assets/images/dashboard_graph1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thongekchakrit/dashboard_dash_mock_sales/HEAD/assets/images/dashboard_graph1.png -------------------------------------------------------------------------------- /assets/images/dashboard_graph2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thongekchakrit/dashboard_dash_mock_sales/HEAD/assets/images/dashboard_graph2.png -------------------------------------------------------------------------------- /datasets/sources/Product Details.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thongekchakrit/dashboard_dash_mock_sales/HEAD/datasets/sources/Product Details.xls -------------------------------------------------------------------------------- /datasets/__pycache__/elt.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thongekchakrit/dashboard_dash_mock_sales/HEAD/datasets/__pycache__/elt.cpython-38.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/extract_load.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thongekchakrit/dashboard_dash_mock_sales/HEAD/datasets/__pycache__/extract_load.cpython-38.pyc -------------------------------------------------------------------------------- /datasets/__pycache__/initialize_datawh.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thongekchakrit/dashboard_dash_mock_sales/HEAD/datasets/__pycache__/initialize_datawh.cpython-38.pyc -------------------------------------------------------------------------------- /assets/images/Product details and Sale Order Details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thongekchakrit/dashboard_dash_mock_sales/HEAD/assets/images/Product details and Sale Order Details.png -------------------------------------------------------------------------------- /datasets/__pycache__/operation_database.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thongekchakrit/dashboard_dash_mock_sales/HEAD/datasets/__pycache__/operation_database.cpython-38.pyc -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Chakrit Thong Ek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /datasets/extract_load.py: -------------------------------------------------------------------------------- 1 | ''' 2 | extract and load source files into operation database 3 | ''' 4 | # Importing required libraries 5 | import pandas as pd 6 | import sqlite3 7 | from sqlite3 import Error 8 | import glob 9 | import os 10 | 11 | # ------------------------------------------------------------------------------ 12 | def main(folder_of_files): 13 | 14 | conn = sqlite3.connect('operation.db') 15 | 16 | files = glob.glob(os.path.join(folder_of_files,"*.xls")) 17 | 18 | if files == []: 19 | raise Exception('\nNo datasets found in DATASETS folder!') 20 | 21 | for file in files: 22 | 23 | # read xls files 24 | xls_file = file 25 | xl = pd.ExcelFile(xls_file) 26 | 27 | # loop through sheets in xls file 28 | # populate database based on tablename 29 | for sheet in xl.sheet_names: 30 | # getting tablename 31 | table_name = sheet.replace(' ', '_').upper() 32 | # getting dataframes 33 | df_tmp = xl.parse(sheet) 34 | df_tmp.to_sql(table_name, conn, if_exists='append', index = False) 35 | 36 | conn.commit() 37 | conn.close() 38 | 39 | # ------------------------------------------------------------------------------ 40 | if __name__ == '__main__': 41 | 42 | try: 43 | main(r'..\datasets\sources') 44 | except Error as e: 45 | print(str(e) + ': Primary keys exists!') 46 | pass -------------------------------------------------------------------------------- /assets/index.css: -------------------------------------------------------------------------------- 1 | /* BASIC TYPOGRAPHY */ 2 | /* from https://github.com/oxalorg/sakura */html { 3 | font-size: 62.5%; 4 | font-family: serif; 5 | } 6 | body { 7 | font-size: 1.8rem; 8 | line-height: 1.618; 9 | max-width: 95em; 10 | margin: auto; 11 | color: #41444b; 12 | background-color: #f3f3f3; 13 | padding: 13px; 14 | padding-top: 25px; 15 | padding: 13px; 16 | padding-top: 25px; 17 | 18 | }@media (max-width: 684px) { 19 | body { 20 | font-size: 1.53rem; 21 | } 22 | }@media (max-width: 382px) { 23 | body { 24 | font-size: 2.35rem; 25 | } 26 | }h1, h2, h3, h4, h5, h6 { 27 | line-height: 1.1; 28 | font-family: Verdana, Geneva, sans-serif; 29 | font-weight: 700; 30 | overflow-wrap: break-word; 31 | word-wrap: break-word; 32 | -ms-word-break: break-all; 33 | word-break: break-word; 34 | -ms-hyphens: auto; 35 | -moz-hyphens: auto; 36 | -webkit-hyphens: auto; 37 | hyphens: auto; 38 | 39 | }h1 { 40 | font-size: 2.35em; 41 | }h2 { 42 | font-size: 2em; 43 | }h3 { 44 | font-size: 1.75em; 45 | }h4 { 46 | font-size: 1.5em; 47 | }h5 { 48 | font-size: 1.25em; 49 | }h6 { 50 | font-size: 1em; 51 | } 52 | 53 | .dash-table-container .row { 54 | margin: 0; 55 | } 56 | 57 | .column { 58 | padding: 10px; 59 | } 60 | 61 | .alert{ 62 | box-shadow : 5px 4px 2.5px #dddddd; 63 | text-align: center; 64 | text-justify: distribute; 65 | font-size: 1.8rem; 66 | height: 50px; 67 | } 68 | 69 | -------------------------------------------------------------------------------- /datasets/initialize_datawh.py: -------------------------------------------------------------------------------- 1 | ''' 2 | create denormalized table for data warehouse 3 | change the table according to business needs 4 | ''' 5 | import sqlite3 6 | import pandas as pd 7 | from sqlite3 import Error 8 | 9 | # ------------------------------------------------------------------------------ 10 | def create_connection(dw_file): 11 | ''' 12 | create connection with data warehouse 13 | ''' 14 | try: 15 | conn = sqlite3.connect(dw_file) 16 | return conn 17 | 18 | except Error as e: 19 | print(e) 20 | 21 | # ------------------------------------------------------------------------------ 22 | def create_table_sql(conn, sql_query): 23 | ''' 24 | create denormalized table from sql_query 25 | ''' 26 | try: 27 | cursor = conn.cursor() 28 | cursor.execute(sql_query) 29 | 30 | except Error as e: 31 | print(e) 32 | 33 | # ------------------------------------------------------------------------------ 34 | def main(): 35 | ''' 36 | create denormolized framework for data 37 | ''' 38 | database = 'data_warehouse.db' 39 | 40 | data_warehouse = ''' 41 | 42 | CREATE TABLE IF NOT EXISTS MASTER_FILE( 43 | Year INT NOT NULL CHECK(length(Year) = 4), 44 | Quarter INT NOT NULL CHECK(Quarter <= 4 AND Quarter >= 1), 45 | CustomerName CHAR(30) NOT NULL, 46 | City CHAR(30) NOT NULL, 47 | Country CHAR(30) NOT NULL, 48 | Sales DECIMAL(10,2) NOT NULL, 49 | Discount DECIMAL(10,2) NOT NULL, 50 | Cost DECIMAL(10,2) NOT NULL, 51 | Profit DECIMAL(15,2) NOT NULL CHECK(Profit > 0), 52 | ProfitType CHAR(6) NOT NULL, 53 | Quantity INT NOT NULL, 54 | Products CHAR(30) NOT NULL, 55 | ProductCategory CHAR(30) NOT NULL, 56 | EmployeeName CHAR(30) NOT NULL, 57 | Latitude FLOAT NOT NULL, 58 | Longitude FLOAT NOT NULL 59 | ); 60 | ''' 61 | 62 | # create a database connection 63 | conn = create_connection(database) 64 | 65 | if conn is not None: 66 | 67 | # create 68 | create_table_sql(conn, data_warehouse) 69 | 70 | conn.commit() 71 | 72 | # ------------------------------------------------------------------------------ 73 | if __name__ == '__main__': 74 | 75 | try: 76 | main() 77 | except: 78 | print('Database exists!') -------------------------------------------------------------------------------- /datasets/elt.py: -------------------------------------------------------------------------------- 1 | ''' 2 | connect with operation database 3 | create a denormalized dataframe for dataware house 4 | ''' 5 | 6 | import pandas as pd 7 | import sqlite3 8 | from sqlalchemy import create_engine 9 | from sqlite3 import Error 10 | 11 | # ------------------------------------------------------------------------------ 12 | def create_connection(db_file): 13 | ''' 14 | create connection with operation database 15 | ''' 16 | try: 17 | conn = sqlite3.connect(db_file) 18 | return conn 19 | 20 | except Error as e: 21 | return print (e) 22 | 23 | # ------------------------------------------------------------------------------ 24 | def create_cursor(conn, sql_query): 25 | ''' 26 | hold the rows returned by sql query 27 | ''' 28 | try: 29 | cursor = conn.cursor() 30 | 31 | except: 32 | print('\nPlease check database path!') 33 | 34 | try: 35 | return cursor.execute(sql_query) 36 | 37 | except UnboundLocalError as u: 38 | print(u) 39 | 40 | else: 41 | print('\nTable in database and query does not match!') 42 | 43 | # ------------------------------------------------------------------------------ 44 | def get_datawarehouse_dataframe(): 45 | ''' 46 | create a dataframe for dataware house 47 | ''' 48 | 49 | db_file = 'operation.db' 50 | 51 | order_details_query = ''' 52 | SELECT 53 | strftime('%Y', OH.OrderDate) as Year, 54 | CASE 55 | WHEN strftime('%m', OH.OrderDate) BETWEEN '01' AND '03' THEN 1 56 | WHEN strftime('%m', OH.OrderDate) BETWEEN '04' AND '06' THEN 2 57 | WHEN strftime('%m', OH.OrderDate) BETWEEN '07' AND '09' THEN 3 58 | ELSE '4' 59 | END AS Quarter, 60 | ContactName as CustomerName, 61 | City, 62 | Country, 63 | round(OD.Sales, 2) as Sales, 64 | round(OD.Discount, 2) as Discount, 65 | round(OD.Costs, 2) as Cost, 66 | round(OD.Profit, 2) as Profit, 67 | Quantity, 68 | CASE 69 | WHEN OD.Profit < 75 THEN 'Small' 70 | WHEN OD.Profit >= 75 AND Profit < 300 THEN 'Medium' 71 | ELSE 'Large' 72 | END AS ProfitType, 73 | P.ProductName as Products, 74 | CAT.CategoryName as ProductCategory, 75 | EmployeeName, 76 | Latitude, 77 | Longitude 78 | FROM 79 | ORDER_DETAILS as OD 80 | INNER JOIN ORDER_HEADER as OH 81 | ON OH.OrderID = OD.OrderID 82 | INNER JOIN CUSTOMERS as C 83 | ON C.CustomerNumber = OH.CustomerID 84 | INNER JOIN PRODUCTS as P 85 | ON P.ProductID = OD.ProductID 86 | INNER JOIN CATEGORIES as CAT 87 | ON CAT.CategoryID = P.CategoryID 88 | INNER JOIN EMPLOYEES as EM 89 | ON EM.EmployeeID = OH.EmployeeID 90 | ''' 91 | 92 | try: 93 | conn = create_connection(db_file) 94 | except: 95 | print('\nCannot create connection to database!') 96 | 97 | try: 98 | cursor = create_cursor(conn, order_details_query) 99 | except: 100 | print('\nTable cannot be found!') 101 | 102 | try: 103 | master_df = pd.DataFrame(cursor, columns=[i[0] for i in cursor.description]) 104 | except: 105 | print('\nCannot convert query into dataframe!') 106 | 107 | return master_df 108 | 109 | # ------------------------------------------------------------------------------ 110 | if __name__ == '__main__': 111 | 112 | get_datawarehouse_dataframe() 113 | 114 | 115 | -------------------------------------------------------------------------------- /datasets/operation_database.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Setting up tables for operation database 3 | ''' 4 | import sqlite3 5 | from sqlite3 import Error 6 | 7 | # ------------------------------------------------------------------------------ 8 | def create_connection(db_file): 9 | ''' 10 | create a database connection to sqlite database 11 | ''' 12 | try: 13 | conn = sqlite3.connect(db_file) 14 | return conn 15 | 16 | except Error as e: 17 | print(e) 18 | 19 | return conn 20 | 21 | # ------------------------------------------------------------------------------ 22 | def create_table(conn, create_table_sql): 23 | ''' 24 | create a table from the create_table_sql statement 25 | ''' 26 | try: 27 | cursor = conn.cursor() 28 | cursor.execute(create_table_sql) 29 | 30 | except Error as e: 31 | print(e) 32 | 33 | # ------------------------------------------------------------------------------ 34 | def main(): 35 | ''' 36 | create all tables 37 | ''' 38 | database = 'operation.db' 39 | 40 | products = ''' 41 | 42 | CREATE TABLE IF NOT EXISTS PRODUCTS( 43 | ProductID INTEGER NOT NULL UNIQUE PRIMARY KEY, 44 | ProductName CHAR(50), 45 | CategoryID INTEGER NOT NULL, 46 | SupplierID INTEGER NOT NULL 47 | ); 48 | ''' 49 | 50 | categories = ''' 51 | 52 | CREATE TABLE IF NOT EXISTS CATEGORIES( 53 | CategoryID INTEGER NOT NULL UNIQUE PRIMARY KEY, 54 | CategoryName CHAR(50), 55 | Description CHAR(50) 56 | ); 57 | ''' 58 | 59 | suppliers = ''' 60 | 61 | CREATE TABLE IF NOT EXISTS SUPPLIERS( 62 | SupplierID INTEGER NOT NULL UNIQUE PRIMARY KEY, 63 | Supplier CHAR(50), 64 | SupplierContact CHAR(50), 65 | SupplierCountry CHAR(50) 66 | ); 67 | ''' 68 | 69 | order_header = ''' 70 | 71 | CREATE TABLE IF NOT EXISTS ORDER_HEADER( 72 | OrderID INTEGER NOT NULL UNIQUE PRIMARY KEY, 73 | OrderDate DATETIME, 74 | CustomerID INTEGER, 75 | EmployeeID INTEGER, 76 | FOREIGN KEY(CustomerID) REFERENCES CUSTOMERS(CustomerNumber), 77 | FOREIGN KEY(EmployeeID) REFERENCES EMPLOYEES(EmployeeID) 78 | ); 79 | ''' 80 | 81 | employees = ''' 82 | 83 | CREATE TABLE IF NOT EXISTS EMPLOYEES( 84 | EmployeeID INTEGER NOT NULL UNIQUE PRIMARY KEY, 85 | EmployeeName CHAR(30) NOT NULL 86 | ); 87 | ''' 88 | 89 | customers = ''' 90 | 91 | CREATE TABLE IF NOT EXISTS CUSTOMERS( 92 | CustomerNumber INTEGER NOT NULL UNIQUE PRIMARY KEY, 93 | ContactName CHAR(30), 94 | City CHAR(25), 95 | Country CHAR(50), 96 | Fax CHAR(15), 97 | Phone CHAR(15), 98 | Latitude FLOAT, 99 | Longitude FLOAT, 100 | Region CHAR(15), 101 | EmployeeID INTEGER 102 | ); 103 | ''' 104 | 105 | order_details = ''' 106 | 107 | CREATE TABLE IF NOT EXISTS ORDER_DETAILS( 108 | ProductID INTEGER NOT NULL, 109 | OrderID INTEGER NOT NULL, 110 | Quantity INTEGER, 111 | Sales DECIMAL (15, 2), 112 | Discount DECIMAL(8,2), 113 | Costs DECIMAL(8,2), 114 | Profit DECIMAL(8,2), 115 | FOREIGN KEY(ProductID) REFERENCES PRODUCTS(ProductID), 116 | FOREIGN KEY(OrderID) REFERENCES ORDER_HEADER(OrderID), 117 | PRIMARY KEY(ProductID, OrderID) 118 | ); 119 | ''' 120 | 121 | # create a database connection 122 | conn = create_connection(database) 123 | 124 | # create Tables 125 | 126 | if conn is not None: 127 | 128 | # create products table 129 | create_table(conn, products) 130 | 131 | # create categories table 132 | create_table(conn, categories) 133 | 134 | # create supplier table 135 | create_table(conn, suppliers) 136 | 137 | # create order_header table 138 | create_table(conn, order_header) 139 | 140 | # create employee table 141 | create_table(conn, employees) 142 | 143 | # create customers table 144 | create_table(conn, customers) 145 | 146 | # create order_details table 147 | create_table(conn, order_details) 148 | 149 | 150 | conn.commit() 151 | 152 | else: 153 | print('\nError cannot create database connection.') 154 | 155 | # ------------------------------------------------------------------------------ 156 | if __name__ == '__main__': 157 | try: 158 | main() 159 | except: 160 | print('\nDatabase exists!') 161 | 162 | -------------------------------------------------------------------------------- /datasets/DBmanager.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | if operation database does not exist, 4 | create operation database and populate database 5 | with data from xls files. 6 | 7 | next, create data warehouse 8 | create data warehouse tables 9 | extract transformed data from operation.db 10 | populate the data warehouse with extracted data 11 | ''' 12 | 13 | import elt 14 | import extract_load 15 | import glob 16 | import initialize_datawh 17 | import operation_database 18 | import os 19 | import sqlite3 20 | from sqlite3 import Error 21 | 22 | # ------------------------------------------------------------------------------ 23 | # locating operation database 24 | file_path = r'../datasets/sources' 25 | 26 | def create_operation_database(file_path): 27 | ''' 28 | create operation database 29 | populate operation database 30 | ''' 31 | # setting up tables for operation database 32 | operation_database.main() 33 | 34 | try: 35 | # extract and load source files into operation database 36 | extract_load.main(file_path) 37 | print('\nDatabase created and table populated!') 38 | 39 | except Error as e: 40 | print(str(e) + ': Primary key exists!') 41 | pass 42 | 43 | # ------------------------------------------------------------------------------ 44 | def get_master_data_file(): 45 | ''' 46 | create connection with operation database 47 | create a denormalized dataframe for dataware house 48 | ''' 49 | 50 | try: 51 | return elt.get_datawarehouse_dataframe() 52 | 53 | except Error as e: 54 | print(e) 55 | 56 | # ------------------------------------------------------------------------------ 57 | def check_operation_exists(): 58 | ''' 59 | check if operation.db exists in database_management folder 60 | ''' 61 | 62 | # pointing to database_management folder 63 | folder_of_files = r'..\datasets' 64 | 65 | # search if 'operation.db' exists in folder 66 | files = glob.glob(os.path.join(folder_of_files, 'operation.db')) 67 | 68 | # return true if exists 69 | return True if files != [] else False 70 | 71 | # ------------------------------------------------------------------------------ 72 | def check_datawh_exists(): 73 | ''' 74 | check if data_warehouse.db exists 75 | ''' 76 | 77 | #pointing to dashboard folder 78 | folder_of_files = r'..\datasets' 79 | 80 | #search if data warehouse exists 81 | files = glob.glob(os.path.join(folder_of_files, 'data_warehouse.db')) 82 | 83 | # return true if exists 84 | return True if files != [] else False 85 | 86 | # ------------------------------------------------------------------------------ 87 | def main(): 88 | 89 | checker_operation = check_operation_exists() 90 | checker_warehouse = check_datawh_exists() 91 | 92 | if checker_operation != True: 93 | 94 | print('\nCreating operation database!') 95 | # set up operation database 96 | create_operation_database(file_path) 97 | # get master df 98 | # used for dataware house insertion 99 | master_df = get_master_data_file() 100 | print('\nGenerating dataframe for data warehouse!') 101 | 102 | else: 103 | print('\nOperation database exists, skipping to dataframe generation!') 104 | # perform dataframe extraction for data warehouse 105 | # if operation.db is detected 106 | master_df = get_master_data_file() 107 | print('\nDataframe for data warehouse generated successfully') 108 | 109 | if checker_warehouse != True: 110 | 111 | # set up data warehouse frame 112 | initialize_datawh.main() 113 | # insert master_df into data_warehouse 114 | try: 115 | conn = sqlite3.connect('data_warehouse.db') 116 | master_df.to_sql('MASTER_FILE' ,conn, if_exists='append', index = False) 117 | print('\nData warehouse has been populated!') 118 | conn.commit() 119 | 120 | except Error as e: 121 | print(e) 122 | 123 | else: 124 | # PROBLEM STATMENT 125 | # THE DATA WAREHOUSE FORMAT GETS RESET 126 | # FIND A WORK AROUND WAY FOR THIS 127 | try: 128 | # insert master_df into data_warehouse 129 | conn = sqlite3.connect('data_warehouse.db') 130 | master_df.to_sql('MASTER_FILE' ,conn, if_exists='append', index = False) 131 | print('\nData warehouse has been updated!') 132 | conn.commit() 133 | 134 | except Error as e: 135 | print(e) 136 | 137 | # ------------------------------------------------------------------------------ 138 | if __name__ == '__main__': 139 | 140 | # run tasks 141 | main() 142 | 143 | 144 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Building Web Application Dashboard Using Python (Dash) 2 | 3 | 4 | 5 | The business intelligence dashboard is an important analytics tool that is used to visualize data across all industries. Using these dashboards, useful insights to Business Performance Management such as the critical reporting and metric information can be readily displayed and used by various departments to drive actionable business decisions. 6 | 7 | To help drive and generate better business decisions, the analytics team in Mock Company had developed a web application using Python programming language with the help of the Dash module. Our application aimed to aid the sales department in providing historic sales and profitability performance, and visualization of various aspects such as geographical sales performance monitoring and sales against profit by product category. 8 | 9 | This documentation highlights the prerequisites, data pipeline, and the demonstration of the use of the application. 10 | 11 | ## 1. Getting Started 12 | The instructions below will help you set up the environment. 13 | 14 | Prerequisites 15 | To use the program, ensure that [Python v3](https://www.python.org/downloads/) and the following libraries are installed on your operating system: 16 | 17 | ``` 18 | 1. Dash 19 | 2. Plotly 20 | 3. Dash-bootstrap-components 21 | 4. sqlalchemy 22 | 5. Pandas 23 | ``` 24 | 25 | If you do not have the modules installed on your system, please follow the instruction below. 26 | 27 | 28 | Installation on Windows using terminal: 29 | ``` 30 | py -m pip install dash 31 | py -m pip install plotly 32 | py -m pip install dash-bootstrap-components 33 | py -m pip install sqlalchemy 34 | py -m pip install pandas 35 | ``` 36 | 37 | Installation on Linux using terminal: 38 | 39 | ``` 40 | $ pip install dash 41 | $ pip install plotly 42 | $ pip install dash-bootstrap-components 43 | $ pip install sqlalchemy 44 | $ pip install pandas 45 | ``` 46 | 47 | ## 2. Data Pipeline 48 | Below is an illustration of the data flow for our application. 49 | 50 | Firstly, the data was extracted, loaded and normalized before insertion into a database via SQlite. 51 | 52 | Next, we then query the data from the database, perform ELT techniques, denormalized the data and transfer the data to a data warehouse. 53 | 54 | Lastly, we query the denormalized data from the data warehouse, run it through our application, and our application will visualize the data onto a web address. 55 | 56 | 57 | 58 | ## 3. Normalized Database Schema 59 | 60 | 61 | 62 | ## 4. Folder Structure 63 | The structure of the Dash application is presented below: 64 | 65 | ``` 66 | - app.py 67 | - assets 68 | |-- index.css 69 | -images 70 | |-- logo.png 71 | - datasets 72 | |-- DBmanager.py 73 | |-- elt.py 74 | |-- extract_load.py 75 | |-- initialize_datawh.py 76 | |-- operation_database.py 77 | |-- data_warehouse.db (Generated with DBmanager.py) 78 | |-- operation.db (Generated with DBmanager.py) 79 | -sources 80 | |-- Product Details.xls 81 | |-- Sales Orders.xls 82 | ``` 83 | 84 | ## 5. Demonstration 85 | The processes of running this application will be shown in this section. 86 | 87 | To run the program, firstly, we need to setup and populate both the database and data warehouse. This process will be performed using DBmanager.py and it can be performed as below. 88 | 89 | ``` 90 | Type in cmd: 91 | cd Desktop 92 | cd Dashboard_sales_orders_product 93 | DBmanager.py 94 | ``` 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | After the population of database and data warehouse, the next step is to run the Dash application using app.py file. The Dash application will start, and as we set the app.py to run in development mode, a development IP server will be generated. 105 | 106 | ``` 107 | Type in cmd: 108 | app.py 109 | ``` 110 | 111 | 112 | 113 | Copy 'http://127.0.0.1:8050/' and insert it into any browser to show our dashboard. 114 | 115 | Capture.PNG 116 | 117 | In total, there are 4 visualization and 6 KPI shown. 118 | 119 | 120 | 121 | 122 | 123 | The data points on the data visualization can be hovered. 124 | 125 | 126 | 127 | Using dash callback python declarators, the first data figure can be interacted with. I've made a drop down menu, which parses in the variable to the plotly graph, and the graph will be generating depending on the variable. 128 | 129 | 130 | 131 | 132 | 133 | ## 6. Future implementation 134 | The future implementation of Conversational Analytics may be done using dash_core_components 'input' syntax. 135 | 136 | https://dash.plotly.com/dash-core-components/input 137 | 138 | ## Built With 139 | - Dash - Create Dashboard Web application, interact with HTML and CSS syntax 140 | - Sqlite3 - Simulate Data warehouse 141 | - Plotly - Use to visualize data 142 | 143 | ## Author 144 | [Chakrit Thong Ek](https://github.com/thongekchakrit) -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import dash 4 | import dash_bootstrap_components as dbc 5 | import dash_core_components as dcc 6 | import dash_html_components as html 7 | import dash_table 8 | from dash.dependencies import Input, Output 9 | import plotly.express as px 10 | import plotly.graph_objects as go 11 | 12 | import pandas as pd 13 | from sqlite3 import Error 14 | import sqlite3 15 | global master_df 16 | 17 | # ################################################################################ 18 | ''' 19 | create connection with data warehouse 20 | extract information of interest from data warehouse 21 | ''' 22 | 23 | def create_connection(): 24 | 25 | try: 26 | # create connection with data warehouse 27 | conn = sqlite3.connect(r'.\datasets\data_warehouse.db') 28 | return conn 29 | 30 | except Error as e: 31 | print(e) 32 | 33 | def fetch_master_df(conn): 34 | try: 35 | # create cursor to get data 36 | cursor = conn.cursor() 37 | 38 | # insert query into cursor 39 | cursor.execute(''' 40 | SELECT * FROM MASTER_FILE 41 | ''') 42 | 43 | # convert query into dataframe 44 | return pd.DataFrame(cursor.fetchall(), columns=[i[0] for i in cursor.description]) 45 | 46 | except Error as e: 47 | print(e) 48 | 49 | # ################################################################################ 50 | # master file for satisfy visualization requirements 51 | 52 | # create connection 53 | conn = create_connection() 54 | 55 | # get full data frame 56 | master_df = fetch_master_df(conn) 57 | 58 | # ------------------------------------------------------------------------------ 59 | # KPI and measures 60 | def get_revenue(master_df): 61 | 62 | try: 63 | return '$ {0:,.2f}K'.format((master_df['Sales'].sum()/1000)) 64 | 65 | except Error as e: 66 | print(e) 67 | pass 68 | 69 | def get_expenses(master_df): 70 | 71 | try: 72 | return '$ {0:,.2f}K'.format((master_df['Cost'].sum()/1000)) 73 | 74 | except Error as e: 75 | print(e) 76 | pass 77 | 78 | def get_sales_quantity(master_df): 79 | try: 80 | return '{0:} Units'.format((master_df['Quantity'].sum())) 81 | 82 | except Error as e: 83 | print(e) 84 | pass 85 | 86 | def get_discount(master_df): 87 | try: 88 | return '$ {0:,.2f}K'.format((master_df['Discount'].sum()/1000)) 89 | 90 | except Error as e: 91 | print(e) 92 | pass 93 | 94 | def get_profit(master_df): 95 | try: 96 | return '$ {0:,.2f}K'.format((master_df['Profit'].sum()/1000)) 97 | 98 | except Error as e: 99 | print(e) 100 | pass 101 | 102 | def get_sales_margin(master_df): 103 | try: 104 | return '{:,.2f}%'.format(((master_df['Profit'].sum())/(master_df['Sales'].sum()))*100) 105 | 106 | except Error as e: 107 | print(e) 108 | pass 109 | 110 | # ------------------------------------------------------------------------------ 111 | # Scatter plot of Sales vs Profit by Category 112 | requirement_iii = master_df[['Sales', 'Discount', 'Cost', 'Profit', 'ProductCategory']].groupby('ProductCategory').sum().reset_index() 113 | 114 | viz_scatter_iii = px.scatter(requirement_iii, x="Sales", y="Profit", size="Profit", color="ProductCategory", 115 | hover_name="Profit", log_x=True, log_y=True, size_max=50, template="seaborn", title='SALES AGAINST PROFIT BY PRODUCT CATEGORY') 116 | 117 | layout = viz_scatter_iii.update_layout( 118 | hoverlabel=dict( 119 | bgcolor="white", 120 | font_size=16, 121 | font_family="Open Sans" 122 | ), 123 | height=700, 124 | width=700, 125 | title={ 126 | 'y':0.9, 127 | 'x':0.5, 128 | 'xanchor': 'center', 129 | 'yanchor': 'top'}, 130 | font=dict( 131 | size=16, 132 | color="#4a4a4a", 133 | ),paper_bgcolor="#f8f9fa") 134 | 135 | viz_scatter_iii.update_traces(mode="markers", hovertemplate='Sales: %{x}
Profit: %{y}') 136 | 137 | # ------------------------------------------------------------------------------ 138 | # Sales by City 139 | # get sales from master file, aggregate on country 140 | city_sales = master_df[['Sales','Profit','Cost','Quantity','Discount','City']].groupby('City').sum() 141 | lat_log_city = master_df[['City', 'Latitude', 'Longitude']].drop_duplicates().set_index('City') 142 | requirement_ii = pd.concat([city_sales, lat_log_city], axis=1, join='inner').reset_index() 143 | requirement_ii['Field'] = ' City' +'
' + requirement_ii['City'] + '
Sales ' + '$' + (requirement_ii['Sales'].round(2).astype(str)) 144 | 145 | mapbox_access_token = 'pk.eyJ1IjoiY2hha3JpdHRob25nZWsiLCJhIjoiY2tkdTAzd2hwMDBkZzJycWluMnFicXFhdCJ9.JjJhMoek5126u1B_kwYNiA' 146 | 147 | px.set_mapbox_access_token(mapbox_access_token) 148 | 149 | map_data = px.scatter_mapbox(requirement_ii, lat="Latitude", lon="Longitude", color="Sales", size="Sales", 150 | color_continuous_scale=px.colors.cyclical.Edge, size_max=20, zoom=1, 151 | center=dict(lon=-40.200033, lat=32.249974), 152 | hover_data={'City':False, 'Latitude':False, 'Longitude':False, 'Sales':False, 'Field':True}, title='SALES BY CITY', template="seaborn") 153 | 154 | map_data.update_layout( 155 | hoverlabel=dict( 156 | bgcolor="white", 157 | font_size=16, 158 | font_family="Open Sans" 159 | ), 160 | height=600, 161 | width=700, 162 | title={ 163 | 'y':0.9, 164 | 'x':0.5, 165 | 'xanchor': 'center', 166 | 'yanchor': 'top'}, 167 | font=dict( 168 | size=16, 169 | color="#4a4a4a"), 170 | paper_bgcolor="#f8f9fa" 171 | ) 172 | 173 | # -------------------------------------------------------------------------------- 174 | # Dash Table 175 | data_table = master_df[['Year', 'Quarter', 'Country', 'City', 'CustomerName', 'ProfitType', 'ProductCategory', 'Sales', 'Profit', 'Cost']] 176 | 177 | # ################################################################################ 178 | # initilize dash 179 | # Bootstrap Javascript. 180 | 181 | BS = "https://stackpath.bootstrapcdn.com/bootswatch/4.5.2/litera/bootstrap.min.css" 182 | 183 | app = dash.Dash(__name__, external_stylesheets=[BS]) 184 | server = app.server 185 | 186 | app.title = 'Mock Company Dashboard' 187 | # Define app layout 188 | 189 | app.layout = html.Div( 190 | html.Div([ 191 | html.Div( 192 | [ 193 | html.H1( 194 | 'Sales Department Data Analysis', 195 | style={ 196 | 'fontSize':40, 197 | 'textAlign':'center', 198 | 'textDirection': 'vertical', 199 | 'dir':'rtl', 200 | 'padding': '20px', 201 | 'padding-top': '70px', 202 | 'color': '#41444b', 203 | 'margin-left':'auto', 204 | 'margin-right':'auto' 205 | }, 206 | className='eight columns'), 207 | 208 | html.Img( 209 | src="/assets/images/logo.png", 210 | className='four columns', 211 | style={ 212 | 'height': '7%', 213 | 'width': '7%', 214 | 'float': 'left', 215 | 'position': 'relative', 216 | 'margin-top': 10, 217 | 'align':'center' 218 | }, 219 | ), 220 | ], className="row" 221 | ), 222 | 223 | dbc.Row([ 224 | 225 | dbc.Col(html.Div( 226 | dbc.Alert("QUANTITY " + 227 | get_sales_quantity(master_df), color='light')), width=2), 228 | 229 | dbc.Col(html.Div( 230 | dbc.Alert("REVENUE " + 231 | get_revenue(master_df), color='light',)), width=2), 232 | 233 | dbc.Col(html.Div( 234 | dbc.Alert("EXPENSES " + 235 | get_expenses(master_df), color='light')), width=2), 236 | 237 | dbc.Col(html.Div( 238 | dbc.Alert("DISCOUNT " + 239 | get_discount(master_df), color='light')), width=2), 240 | 241 | dbc.Col(html.Div( 242 | dbc.Alert("PROFIT " + 243 | get_profit(master_df), color='light')), width=2), 244 | 245 | dbc.Col(html.Div( 246 | dbc.Alert("MARGIN " + 247 | get_sales_margin(master_df), color='light')), width=2), 248 | 249 | ], align="center", 250 | justify="center"), 251 | 252 | html.Div( 253 | [ 254 | html.Div([ 255 | dcc.Dropdown( 256 | id = 'Cities', 257 | options=[ 258 | {'label': 'Country', 'value': 'Country'}, 259 | {'label': 'Employee', 'value': 'EmployeeName'} 260 | ], 261 | value='Country', 262 | style={'height': 'auto', 'width': '700px', 'align':'center'} 263 | ), 264 | dcc.Graph( 265 | id='bar_graph' 266 | ) 267 | ], className= 'six columns', 268 | style={'border-radius': '15px', 269 | 'backgroundColor': '#f8f9fa', 270 | 'box-shadow' : '4px 4px 2.5px #dddddd', 271 | 'padding':'20px', 'margin-left':'auto','margin-right':'auto', 'margin-top':'25px'} 272 | ), 273 | 274 | html.Div([ 275 | dcc.Graph( 276 | id='graph-4', 277 | figure=map_data 278 | ) 279 | ], className= 'six columns', 280 | style={'border-radius': '15px', 281 | 'backgroundColor': '#f8f9fa', 282 | 'box-shadow' : '4px 4px 2.5px #dddddd', 283 | 'padding':'20px', 'margin-left':'auto','margin-right':'auto', 'margin-top':'25px'} 284 | ) 285 | ], className="row" 286 | ), 287 | 288 | 289 | html.Div( 290 | [ 291 | html.Div([ 292 | dash_table.DataTable( 293 | id='datatable-interactivity', 294 | data=data_table.to_dict('records'), 295 | columns=[{'name': i, 'id': i, "deletable":True,"selectable":True} for i in data_table.columns], 296 | editable = True, 297 | sort_action="native", 298 | sort_mode="multi", 299 | column_selectable="single", 300 | row_selectable="multi", 301 | row_deletable=True, 302 | selected_columns=[], 303 | selected_rows=[], 304 | page_action="native", 305 | page_current= 0, 306 | page_size=55, 307 | style_cell={ 308 | 'textOverflow': 'ellipsis', 309 | 'overflow': 'hidden' 310 | }, 311 | style_table={ 312 | 'width':'700px', 313 | 'height':'600px', 314 | 'overflowY':'auto', 315 | 'overflowX': 'auto', 316 | 'align': 'center' 317 | }, 318 | style_header={ 319 | 'backgroundColor': 'rgb(230, 230, 230)', 320 | 'fontWeight': 'bold' 321 | } 322 | ) 323 | ], className= 'six columns', 324 | style={'border-radius': '15px', 325 | 'backgroundColor': '#f8f9fa', 326 | 'box-shadow' : '4px 4px 2.5px #dddddd', 327 | 'padding':'20px', 'margin-left':'auto','margin-right':'auto', 'margin-top':'25px'}), 328 | html.Div([ 329 | dcc.Graph( 330 | id='graph-1', figure=viz_scatter_iii 331 | ) 332 | ], className= 'six columns', 333 | style={'border-radius': '15px', 334 | 'backgroundColor': '#f8f9fa', 335 | 'box-shadow' : '4px 4px 2.5px #dddddd', 336 | 'padding':'20px', 'margin-left':'auto','margin-right':'auto', 'margin-top':'25px'} 337 | ), 338 | ], className="row" 339 | ) 340 | ], className='ten columns offset-by-one') 341 | ) 342 | 343 | # ################################################################################ 344 | # Connect the Plotly graphs with Dash Components 345 | 346 | # Interactivity with table 347 | 348 | @app.callback( 349 | dash.dependencies.Output('bar_graph', 'figure'), 350 | [dash.dependencies.Input('Cities', 'value')]) 351 | def select_cat(selector): 352 | revenue_country = master_df[['Country','Sales', 'EmployeeName']] 353 | revenue_country = revenue_country.groupby(selector).sum().reset_index().sort_values('Sales') 354 | 355 | revenue_country_viz = px.bar(revenue_country, y=selector, x='Sales', text='Sales', template="seaborn", 356 | hover_data={'Sales':':.2f'}, title='REVENUE BY ' + str(selector).upper()) 357 | 358 | revenue_country_viz.update_traces(texttemplate='%{text:.2s}', textposition='outside') 359 | revenue_country_viz.update_layout(uniformtext_minsize=7, uniformtext_mode='hide', height=600) 360 | 361 | revenue_country_viz.update_layout( 362 | hoverlabel=dict( 363 | bgcolor="white", 364 | font_size=16, 365 | font_family="Open Sans" 366 | ), 367 | title={ 368 | 'y':0.9, 369 | 'x':0.5, 370 | 'xanchor': 'center', 371 | 'yanchor': 'top'}, 372 | font=dict( 373 | size=16, 374 | color="#4a4a4a" 375 | ), 376 | paper_bgcolor="#f8f9fa") 377 | return revenue_country_viz 378 | # def update_table(input_value): 379 | # return generate_table(df_table.sort_values([input_value], ascending=[False]).reset_index()) 380 | 381 | # @app.callback( 382 | # Output(component_id='bar-chart', component_property='figure'), 383 | # [Input(component_id='bubble-chart', component_property='hoverData')] 384 | # ) 385 | 386 | # def update_graph(master_df): 387 | # return bar(master_df) 388 | 389 | 390 | # ################################################################################ 391 | if __name__ == '__main__': 392 | 393 | app.run_server(debug=True) 394 | --------------------------------------------------------------------------------