├── README.md ├── dags └── surf_dag │ └── main.py ├── docker-compose.yaml ├── processed_data └── 05-12-22-surf-report.csv ├── raw_data ├── 05-12-22-surf-report.csv ├── 05-12-22surf-report.csv ├── 051222surf-report.csv ├── surf-report.csv ├── tides.csv └── waves.csv └── surfdash.py /README.md: -------------------------------------------------------------------------------- 1 | # Surfline Dashboard 2 | 3 | ## **Architecture** 4 | 5 | 6 | Surfline App Architecture 7 | 8 | 9 | ## **Overview** 10 | 11 | The pipeline collects data from the surfline API and exports a csv file to S3. Then the most recent file in S3 is downloaded to be ingested into the Postgres datawarehouse. A temp table is created and then the unique rows are inserted into the data tables. Airflow is used for orchestration and hosted locally with docker-compose and mysql. Postgres is also running locally in a docker container. The data dashboard is run locally with ploty. 12 | 13 | ## **ETL** 14 | 15 | ![image](https://user-images.githubusercontent.com/5299312/169564659-76d6cde9-fc59-4d18-9fc4-d8d6f8fa1c0b.png) 16 | 17 | ## **Data Warehouse - Postgres** 18 | 19 | ![image](https://user-images.githubusercontent.com/5299312/169566679-3d46d244-b139-4414-a406-c5c18d981ac3.png) 20 | 21 | ## **Data Dashboard** 22 | 23 | ![image](https://user-images.githubusercontent.com/5299312/169568656-e6a77014-4bd2-4d21-9236-f24d6f1061b7.png) 24 | 25 | ## **Learning Resources** 26 | 27 | Airflow Basics: 28 | 29 | [Airflow DAG: Coding your first DAG for Beginners](https://www.youtube.com/watch?v=IH1-0hwFZRQ) 30 | 31 | [Running Airflow 2.0 with Docker in 5 mins](https://www.youtube.com/watch?v=aTaytcxy2Ck) 32 | 33 | S3 Basics: 34 | 35 | [Setting Up Airflow Tasks To Connect Postgres And S3](https://www.youtube.com/watch?v=30VDVVSNLcc) 36 | 37 | [How to Upload files to AWS S3 using Python and Boto3](https://www.youtube.com/watch?v=G68oSgFotZA) 38 | 39 | [Download files from S3](https://www.stackvidhya.com/download-files-from-s3-using-boto3/) 40 | 41 | Docker Basics: 42 | 43 | [Docker Tutorial for Beginners](https://www.youtube.com/watch?v=3c-iBn73dDE) 44 | 45 | [Docker and PostgreSQL](https://www.youtube.com/watch?v=aHbE3pTyG-Q) 46 | 47 | [Build your first pipeline DAG | Apache airflow for beginners](https://www.youtube.com/watch?v=28UI_Usxbqo) 48 | 49 | [Run Airflow 2.0 via Docker | Minimal Setup | Apache airflow for beginners](https://www.youtube.com/watch?v=TkvX1L__g3s&t=389s) 50 | 51 | [Docker Network Bridge](https://docs.docker.com/network/bridge/) 52 | 53 | [Docker Curriculum](https://docker-curriculum.com/) 54 | 55 | [Docker Compose - Airflow](https://medium.com/@rajat.mca.du.2015/airflow-and-mysql-with-docker-containers-80ed9c2bd340) 56 | 57 | Plotly: 58 | 59 | [Introduction to Plotly](https://www.youtube.com/watch?v=hSPmj7mK6ng) 60 | -------------------------------------------------------------------------------- /dags/surf_dag/main.py: -------------------------------------------------------------------------------- 1 | # We'll start by importing the DAG object 2 | from datetime import timedelta 3 | 4 | from airflow import DAG 5 | # We need to import the operators used in our tasks 6 | from airflow.operators.python_operator import PythonOperator 7 | # We then import the days_ago function 8 | from airflow.utils.dates import days_ago 9 | 10 | import pandas as pd 11 | import psycopg2 12 | import os 13 | from pysurfline import SurfReport 14 | import boto3 15 | from datetime import date 16 | 17 | 18 | # get dag directory path 19 | dag_path = os.getcwd() 20 | 21 | 22 | def download_data(): 23 | params = { 24 | "spotId": "5842041f4e65fad6a77087f9", 25 | "days": 1, 26 | "intervalHours": 1,} 27 | 28 | today = date.today() 29 | surfdate = today.strftime("%m-%d-%y") 30 | 31 | report = SurfReport(params) 32 | print(report.api_log) 33 | 34 | surf_report = report.df.drop(columns=['utcOffset','swells']) 35 | surf_report.to_csv(dag_path+'/raw_data/'+ surfdate +'-surf-report.csv',header=False) 36 | 37 | 38 | def load_s3_data(): 39 | today = date.today() 40 | surfdate = today.strftime("%m-%d-%y") 41 | session = boto3.Session( 42 | aws_access_key_id="", 43 | aws_secret_access_key="",) 44 | s3 = session.resource('s3') 45 | 46 | s3.meta.client.upload_file(dag_path+'/raw_data/'+ surfdate +'-surf-report.csv','wavestorm',surfdate +'-surf-report.csv') 47 | 48 | 49 | def download_s3_data(): 50 | 51 | s3 = boto3.client('s3',aws_access_key_id="", 52 | aws_secret_access_key="") 53 | 54 | get_last_modified = lambda obj: int(obj['LastModified'].strftime('%s')) 55 | 56 | objs = s3.list_objects_v2(Bucket='wavestorm')['Contents'] 57 | last_added = [obj['Key'] for obj in sorted(objs, key=get_last_modified)][-1] 58 | 59 | session = boto3.Session( 60 | aws_access_key_id="", 61 | aws_secret_access_key="",) 62 | s3 = session.resource('s3') 63 | 64 | 65 | s3.Bucket('wavestorm').download_file(last_added,dag_path+'/processed_data/'+last_added) 66 | 67 | 68 | def load_data(): 69 | #establishing the connection 70 | conn = psycopg2.connect( 71 | database="storm", user='postgres', password='wavestorm', host='172.17.0.1', port= '5432' 72 | ) 73 | #Creating a cursor object using the cursor() method 74 | cursor = conn.cursor() 75 | 76 | #Executing an MYSQL function using the execute() method 77 | cursor.execute("select version()") 78 | 79 | # Fetch a single row using fetchone() method. 80 | data = cursor.fetchone() 81 | print("Connection established to: ",data) 82 | 83 | s3 = boto3.client('s3',aws_access_key_id="", 84 | aws_secret_access_key="") 85 | 86 | get_last_modified = lambda obj: int(obj['LastModified'].strftime('%s')) 87 | 88 | objs = s3.list_objects_v2(Bucket='wavestorm')['Contents'] 89 | last_added = [obj['Key'] for obj in sorted(objs, key=get_last_modified)][-1] 90 | 91 | command = ( 92 | """ 93 | 94 | CREATE TEMPORARY TABLE IF NOT EXISTS staging_surf_report ( 95 | timestamp TIMESTAMP PRIMARY KEY, 96 | surf_min INTEGER, 97 | surf_max INTEGER, 98 | surf_optimalScore INTEGER, 99 | surf_plus BOOL, 100 | surf_humanRelation VARCHAR(255), 101 | surf_raw_min NUMERIC, 102 | surf_raw_max NUMERIC, 103 | speed NUMERIC, 104 | direction NUMERIC, 105 | directionType VARCHAR(255), 106 | gust NUMERIC, 107 | optimalScore INTEGER, 108 | temperature NUMERIC, 109 | condition VARCHAR(255) 110 | ); 111 | """) 112 | 113 | 114 | print(command) 115 | cursor.execute(command) 116 | conn.commit() 117 | 118 | 119 | print(last_added) 120 | f = open(dag_path+'/processed_data/'+last_added, 'r') 121 | print(f) 122 | try: 123 | cursor.copy_from(f, 'staging_surf_report', sep=",") 124 | print("Data inserted using copy_from_datafile() successfully....") 125 | except (Exception, psycopg2.DatabaseError) as err: 126 | #os.remove(dag_path+'/processed_data/'+last_added) 127 | # pass exception to function 128 | print(psycopg2.DatabaseError) 129 | print(Exception) 130 | show_psycopg2_exception(err) 131 | cursor.close() 132 | conn.commit() 133 | 134 | 135 | command = (""" 136 | 137 | INSERT INTO surf_report 138 | (timestamp, surf_min,surf_max,surf_optimalScore,surf_plus,surf_humanRelation,surf_raw_min,surf_raw_max,speed,direction,directionType,gust,optimalScore,temperature,condition) 139 | SELECT * 140 | FROM staging_surf_report 141 | WHERE NOT EXISTS(SELECT timestamp 142 | FROM surf_report 143 | WHERE staging_surf_report.timestamp = surf_report.timestamp);""") 144 | 145 | 146 | cursor.execute(command) 147 | conn.commit() 148 | conn.close() 149 | 150 | # initializing the default arguments that we'll pass to our DAG 151 | default_args = { 152 | 'owner': 'airflow', 153 | 'start_date': days_ago(5) 154 | } 155 | 156 | ingestion_dag = DAG( 157 | 'surf_dag', 158 | default_args=default_args, 159 | description='Aggregates booking records for data analysis', 160 | schedule_interval=timedelta(days=1), 161 | catchup=False 162 | ) 163 | 164 | task_1 = PythonOperator( 165 | task_id='download_data', 166 | python_callable=download_data, 167 | dag=ingestion_dag, 168 | ) 169 | 170 | task_2 = PythonOperator( 171 | task_id='load_s3_data', 172 | python_callable=load_s3_data, 173 | dag=ingestion_dag, 174 | ) 175 | 176 | task_3 = PythonOperator( 177 | task_id='download_s3_data', 178 | python_callable=download_s3_data, 179 | dag=ingestion_dag, 180 | ) 181 | 182 | 183 | task_4 = PythonOperator( 184 | task_id='load_data', 185 | python_callable=load_data, 186 | dag=ingestion_dag, 187 | ) 188 | 189 | #task_3 190 | task_1 >> task_2 >> task_3 >> task_4 -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | # 18 | 19 | --- 20 | version: '3' 21 | 22 | x-airflow-common: 23 | &airflow-common 24 | image: apache/airflow:2.1.1-python3.8 25 | environment: 26 | &airflow-common-env 27 | AIRFLOW__CORE__SQL_ALCHEMY_CONN: mysql://airflow:airflow@mysql:3306/airflow 28 | AIRFLOW__CORE__LOAD_EXAMPLES: 'false' 29 | _PIP_ADDITIONAL_REQUIREMENTS: ${_PIP_ADDITIONAL_REQUIREMENTS:- pysurfline} 30 | 31 | volumes: 32 | - ./dags:/opt/airflow/dags 33 | - ./logs:/opt/airflow/logs 34 | - ./raw_data:/opt/airflow/raw_data 35 | - ./processed_data:/opt/airflow/processed_data 36 | - ./plugins:/opt/airflow/plugins 37 | user: "${AIRFLOW_UID:-50000}:${AIRFLOW_GID:-50000}" 38 | depends_on: 39 | mysql: 40 | condition: service_healthy 41 | 42 | services: 43 | mysql: 44 | image: mysql:5.7 45 | command: --explicit_defaults_for_timestamp 46 | ports: 47 | - "3306:3306" 48 | volumes: 49 | - my-db:/var/lib/mysql 50 | environment: 51 | MYSQL_ROOT_USER: airflow 52 | MYSQL_ROOT_PASSWORD: airflow 53 | MYSQL_USER: airflow 54 | MYSQL_PASSWORD: airflow 55 | MYSQL_DATABASE: airflow 56 | healthcheck: 57 | test: "/etc/init.d/mysql status" 58 | interval: 2s 59 | retries: 120 60 | 61 | airflow-init: 62 | <<: *airflow-common 63 | entrypoint: /bin/bash -c "/bin/bash -c \"$${@}\"" 64 | command: | 65 | /bin/bash -c " 66 | airflow db init 67 | airflow db upgrade 68 | airflow users create -r Admin -u admin -e airflow@airflow.com -f admin -l user -p airflow 69 | " 70 | environment: 71 | <<: *airflow-common-env 72 | 73 | airflow-scheduler: 74 | <<: *airflow-common 75 | command: scheduler 76 | environment: 77 | <<: *airflow-common-env 78 | restart: always 79 | 80 | airflow-webserver: 81 | <<: *airflow-common 82 | command: webserver 83 | 84 | ports: 85 | - 8081:8080 86 | healthcheck: 87 | test: ["CMD", "curl", "--fail", "http://localhost:8080/health"] 88 | interval: 10s 89 | timeout: 10s 90 | retries: 5 91 | restart: always 92 | environment: 93 | <<: *airflow-common-env 94 | 95 | networks: 96 | backend: 97 | driver: "bridge" 98 | 99 | # Names our volume 100 | volumes: 101 | my-db: -------------------------------------------------------------------------------- /processed_data/05-12-22-surf-report.csv: -------------------------------------------------------------------------------- 1 | 2022-05-12 07:00:00,3,4,2,False,Waist to chest,3.28084,3.74016,12.31638,304.54605,Onshore,21.77107,0,50.36002,NIGHT_CLEAR 2 | 2022-05-12 10:00:00,2,3,2,True,Thigh to stomach,2.131885,3.12402,11.52399,303.42756,Onshore,22.93737,0,50.17996,NIGHT_CLEAR 3 | 2022-05-12 13:00:00,2,3,2,False,Thigh to waist,2.0,3.0,12.13841,306.29277,Onshore,16.71707,0,49.31599,CLEAR 4 | 2022-05-12 16:00:00,2,3,2,True,Thigh to stomach,2.0787400000000003,3.07874,12.30578,301.93971,Onshore,17.49461,0,48.00203,CLOUDY 5 | 2022-05-12 19:00:00,2,3,2,False,Thigh to waist,2.0,3.0,14.03576,299.88866,Onshore,18.07776,0,48.56002,MOSTLY_CLEAR 6 | 2022-05-12 22:00:00,2,3,0,False,Thigh to waist,1.9409480000000001,2.960634,14.67359,293.18491,Onshore,19.43845,0,49.29797,CLEAR 7 | 2022-05-13 01:00:00,2,3,2,False,Thigh to waist,2.0,3.0,15.55441,301.07205,Onshore,26.24191,0,50.53998,CLEAR 8 | 2022-05-13 04:00:00,2,3,2,True,Thigh to stomach,2.1811000000000003,3.1568199999999997,12.98632,309.05588,Onshore,25.65876,0,50.36002,NIGHT_CLEAR 9 | -------------------------------------------------------------------------------- /raw_data/05-12-22-surf-report.csv: -------------------------------------------------------------------------------- 1 | 2022-05-12 07:00:00,3,4,2,False,Waist to chest,3.28084,3.74016,12.31638,304.54605,Onshore,21.77107,0,50.36002,NIGHT_CLEAR 2 | 2022-05-12 10:00:00,2,3,2,True,Thigh to stomach,2.131885,3.12402,11.52399,303.42756,Onshore,22.93737,0,50.17996,NIGHT_CLEAR 3 | 2022-05-12 13:00:00,2,3,2,False,Thigh to waist,2.0,3.0,12.13841,306.29277,Onshore,16.71707,0,49.31599,CLEAR 4 | 2022-05-12 16:00:00,2,3,2,True,Thigh to stomach,2.0787400000000003,3.07874,12.30578,301.93971,Onshore,17.49461,0,48.00203,CLOUDY 5 | 2022-05-12 19:00:00,2,3,2,False,Thigh to waist,2.0,3.0,14.03576,299.88866,Onshore,18.07776,0,48.56002,MOSTLY_CLEAR 6 | 2022-05-12 22:00:00,2,3,0,False,Thigh to waist,1.9409480000000001,2.960634,14.67359,293.18491,Onshore,19.43845,0,49.29797,CLEAR 7 | 2022-05-13 01:00:00,2,3,2,False,Thigh to waist,2.0,3.0,15.55441,301.07205,Onshore,26.24191,0,50.53998,CLEAR 8 | 2022-05-13 04:00:00,2,3,2,True,Thigh to stomach,2.1811000000000003,3.1568199999999997,12.98632,309.05588,Onshore,25.65876,0,50.36002,NIGHT_CLEAR 9 | -------------------------------------------------------------------------------- /raw_data/05-12-22surf-report.csv: -------------------------------------------------------------------------------- 1 | timestamp,utcOffset,surf_min,surf_max,surf_optimalScore,surf_plus,surf_humanRelation,surf_raw_min,surf_raw_max,swells,utcOffset,speed,direction,directionType,gust,optimalScore,utcOffset,temperature,condition 2 | 2022-05-11 07:00:00,-7,4,5,2,False,Chest to head,4.33071,4.85564,"[{'height': 6.40682, 'period': 10, 'direction': 302.83807, 'directionMin': 291.112125, 'optimalScore': 0}, {'height': 1.62536, 'period': 13, 'direction': 201.36981, 'directionMin': 196.205475, 'optimalScore': 0}, {'height': 0.59469, 'period': 11, 'direction': 163.30792, 'directionMin': 159.731445, 'optimalScore': 0}, {'height': 0.31804, 'period': 19, 'direction': 210.29364, 'directionMin': 204.388295, 'optimalScore': 0}, {'height': 0.26119, 'period': 16, 'direction': 284.66412, 'directionMin': 280.69032000000004, 'optimalScore': 0}, {'height': 0, 'period': 0, 'direction': 0, 'directionMin': 0, 'optimalScore': 0}]",-7,10.02494,320.11238,Onshore,16.71707,0,-7,49.496,NIGHT_CLEAR 3 | 2022-05-11 10:00:00,-7,4,6,2,False,Chest to overhead,3.977035,5.042649999999999,"[{'height': 6.25801, 'period': 10, 'direction': 302.98505, 'directionMin': 291.103995, 'optimalScore': 0}, {'height': 1.57992, 'period': 13, 'direction': 201.73944, 'directionMin': 196.45204, 'optimalScore': 0}, {'height': 0.56624, 'period': 11, 'direction': 163.09503, 'directionMin': 159.51381, 'optimalScore': 0}, {'height': 0.38123, 'period': 19, 'direction': 210.2182, 'directionMin': 203.17021499999998, 'optimalScore': 0}, {'height': 0.28176, 'period': 16, 'direction': 284.17993, 'directionMin': 280.31114, 'optimalScore': 0}, {'height': 0, 'period': 0, 'direction': 0, 'directionMin': 0, 'optimalScore': 0}]",-7,10.48745,320.91681,Onshore,16.52268,0,-7,48.84797,NIGHT_CLEAR 4 | 2022-05-11 13:00:00,-7,3,5,2,False,Waist to head,3.0,5.0,"[{'height': 6.03179, 'period': 9, 'direction': 300.87885, 'directionMin': 289.87952, 'optimalScore': 0}, {'height': 1.61673, 'period': 13, 'direction': 201.97192, 'directionMin': 196.540445, 'optimalScore': 0}, {'height': 0.52618, 'period': 11, 'direction': 162.40509, 'directionMin': 159.051955, 'optimalScore': 0}, {'height': 0.38743, 'period': 19, 'direction': 212.64392, 'directionMin': 205.424225, 'optimalScore': 0}, {'height': 0.29308, 'period': 16, 'direction': 285.64435, 'directionMin': 281.523575, 'optimalScore': 0}, {'height': 0, 'period': 0, 'direction': 0, 'directionMin': 0, 'optimalScore': 0}]",-7,3.07598,346.22836,Cross-shore,6.2203,2,-7,48.16402,CLEAR 5 | 2022-05-11 16:00:00,-7,3,4,2,True,Waist to shoulder,2.986882,4.006562000000001,"[{'height': 6.01742, 'period': 9, 'direction': 300.63123, 'directionMin': 290.34665, 'optimalScore': 0}, {'height': 1.63707, 'period': 13, 'direction': 201.77783, 'directionMin': 196.226775, 'optimalScore': 0}, {'height': 0.49465, 'period': 11, 'direction': 162.26929, 'directionMin': 158.8711, 'optimalScore': 0}, {'height': 0.30253, 'period': 19, 'direction': 222.07281, 'directionMin': 217.174115, 'optimalScore': 0}, {'height': 0.40453, 'period': 15, 'direction': 286.88751, 'directionMin': 283.067675, 'optimalScore': 0}, {'height': 0, 'period': 0, 'direction': 0, 'directionMin': 0, 'optimalScore': 0}]",-7,4.09031,281.21758,Onshore,4.66523,2,-7,47.46199,CLEAR 6 | 2022-05-11 19:00:00,-7,3,4,2,False,Waist to chest,3.0,4.0,"[{'height': 5.96053, 'period': 9, 'direction': 301.50568, 'directionMin': 291.25786, 'optimalScore': 0}, {'height': 1.61959, 'period': 13, 'direction': 201.64026, 'directionMin': 196.010805, 'optimalScore': 0}, {'height': 0.46575, 'period': 11, 'direction': 162.17615, 'directionMin': 158.773615, 'optimalScore': 0}, {'height': 0, 'period': 0, 'direction': 0, 'directionMin': 0, 'optimalScore': 0}, {'height': 0.4939, 'period': 15, 'direction': 286.68591, 'directionMin': 282.90094999999997, 'optimalScore': 0}, {'height': 0.20512, 'period': 16, 'direction': 233.78076, 'directionMin': 231.22822499999998, 'optimalScore': 0}]",-7,14.08923,296.92239,Onshore,17.88338,0,-7,48.97399,CLEAR 7 | 2022-05-11 22:00:00,-7,3,4,2,False,Waist to chest,3.0,4.0,"[{'height': 6.41096, 'period': 9, 'direction': 304.17798, 'directionMin': 293.099495, 'optimalScore': 0}, {'height': 1.56847, 'period': 13, 'direction': 200.98853, 'directionMin': 195.473165, 'optimalScore': 0}, {'height': 0.43786, 'period': 11, 'direction': 162.18292, 'directionMin': 158.72098, 'optimalScore': 0}, {'height': 0, 'period': 0, 'direction': 0, 'directionMin': 0, 'optimalScore': 0}, {'height': 0.49478, 'period': 15, 'direction': 286.41113, 'directionMin': 282.741065, 'optimalScore': 0}, {'height': 0.22703, 'period': 16, 'direction': 233.96613, 'directionMin': 231.31288, 'optimalScore': 0}]",-7,19.22053,304.648,Onshore,26.82506,0,-7,49.29797,CLEAR 8 | 2022-05-12 01:00:00,-7,3,4,2,False,Waist to chest,3.0,4.0,"[{'height': 6.73481, 'period': 9, 'direction': 306.15045, 'directionMin': 294.392065, 'optimalScore': 0}, {'height': 1.45656, 'period': 13, 'direction': 199.93683, 'directionMin': 194.57262999999998, 'optimalScore': 0}, {'height': 0.41066, 'period': 11, 'direction': 162.2345, 'directionMin': 158.72821, 'optimalScore': 0}, {'height': 0.41171, 'period': 18, 'direction': 205.45667, 'directionMin': 200.92344, 'optimalScore': 0}, {'height': 0.42782, 'period': 15, 'direction': 285.66772, 'directionMin': 281.988205, 'optimalScore': 0}, {'height': 0.21837, 'period': 16, 'direction': 234.21588, 'directionMin': 231.396045, 'optimalScore': 0}]",-7,19.86497,308.08838,Onshore,29.15768,0,-7,49.51402,CLEAR 9 | 2022-05-12 04:00:00,-7,3,4,2,True,Waist to shoulder,3.091205,4.066929999999999,"[{'height': 6.6271, 'period': 7, 'direction': 307.7179, 'directionMin': 295.63340999999997, 'optimalScore': 0}, {'height': 1.3435, 'period': 13, 'direction': 200.63428, 'directionMin': 195.42863, 'optimalScore': 0}, {'height': 0.38097, 'period': 11, 'direction': 161.66565, 'directionMin': 158.446305, 'optimalScore': 0}, {'height': 0.59278, 'period': 18, 'direction': 196.81744, 'directionMin': 191.00425, 'optimalScore': 0}, {'height': 0.47575, 'period': 14, 'direction': 287.34473, 'directionMin': 283.49074, 'optimalScore': 0}, {'height': 0.19219, 'period': 16, 'direction': 234.17261, 'directionMin': 231.41558, 'optimalScore': 0}]",-7,16.45581,309.3965,Onshore,24.10368,0,-7,48.43401,NIGHT_CLEAR 10 | -------------------------------------------------------------------------------- /raw_data/051222surf-report.csv: -------------------------------------------------------------------------------- 1 | timestamp,utcOffset,surf_min,surf_max,surf_optimalScore,surf_plus,surf_humanRelation,surf_raw_min,surf_raw_max,swells,utcOffset,speed,direction,directionType,gust,optimalScore,utcOffset,temperature,condition 2 | 2022-05-11 07:00:00,-7,4,5,2,False,Chest to head,4.33071,4.85564,"[{'height': 6.40682, 'period': 10, 'direction': 302.83807, 'directionMin': 291.112125, 'optimalScore': 0}, {'height': 1.62536, 'period': 13, 'direction': 201.36981, 'directionMin': 196.205475, 'optimalScore': 0}, {'height': 0.59469, 'period': 11, 'direction': 163.30792, 'directionMin': 159.731445, 'optimalScore': 0}, {'height': 0.31804, 'period': 19, 'direction': 210.29364, 'directionMin': 204.388295, 'optimalScore': 0}, {'height': 0.26119, 'period': 16, 'direction': 284.66412, 'directionMin': 280.69032000000004, 'optimalScore': 0}, {'height': 0, 'period': 0, 'direction': 0, 'directionMin': 0, 'optimalScore': 0}]",-7,10.02494,320.11238,Onshore,16.71707,0,-7,49.496,NIGHT_CLEAR 3 | 2022-05-11 10:00:00,-7,4,6,2,False,Chest to overhead,3.977035,5.042649999999999,"[{'height': 6.25801, 'period': 10, 'direction': 302.98505, 'directionMin': 291.103995, 'optimalScore': 0}, {'height': 1.57992, 'period': 13, 'direction': 201.73944, 'directionMin': 196.45204, 'optimalScore': 0}, {'height': 0.56624, 'period': 11, 'direction': 163.09503, 'directionMin': 159.51381, 'optimalScore': 0}, {'height': 0.38123, 'period': 19, 'direction': 210.2182, 'directionMin': 203.17021499999998, 'optimalScore': 0}, {'height': 0.28176, 'period': 16, 'direction': 284.17993, 'directionMin': 280.31114, 'optimalScore': 0}, {'height': 0, 'period': 0, 'direction': 0, 'directionMin': 0, 'optimalScore': 0}]",-7,10.48745,320.91681,Onshore,16.52268,0,-7,48.84797,NIGHT_CLEAR 4 | 2022-05-11 13:00:00,-7,3,5,2,False,Waist to head,3.0,5.0,"[{'height': 6.03179, 'period': 9, 'direction': 300.87885, 'directionMin': 289.87952, 'optimalScore': 0}, {'height': 1.61673, 'period': 13, 'direction': 201.97192, 'directionMin': 196.540445, 'optimalScore': 0}, {'height': 0.52618, 'period': 11, 'direction': 162.40509, 'directionMin': 159.051955, 'optimalScore': 0}, {'height': 0.38743, 'period': 19, 'direction': 212.64392, 'directionMin': 205.424225, 'optimalScore': 0}, {'height': 0.29308, 'period': 16, 'direction': 285.64435, 'directionMin': 281.523575, 'optimalScore': 0}, {'height': 0, 'period': 0, 'direction': 0, 'directionMin': 0, 'optimalScore': 0}]",-7,3.07598,346.22836,Cross-shore,6.2203,2,-7,48.16402,CLEAR 5 | 2022-05-11 16:00:00,-7,3,4,2,True,Waist to shoulder,2.986882,4.006562000000001,"[{'height': 6.01742, 'period': 9, 'direction': 300.63123, 'directionMin': 290.34665, 'optimalScore': 0}, {'height': 1.63707, 'period': 13, 'direction': 201.77783, 'directionMin': 196.226775, 'optimalScore': 0}, {'height': 0.49465, 'period': 11, 'direction': 162.26929, 'directionMin': 158.8711, 'optimalScore': 0}, {'height': 0.30253, 'period': 19, 'direction': 222.07281, 'directionMin': 217.174115, 'optimalScore': 0}, {'height': 0.40453, 'period': 15, 'direction': 286.88751, 'directionMin': 283.067675, 'optimalScore': 0}, {'height': 0, 'period': 0, 'direction': 0, 'directionMin': 0, 'optimalScore': 0}]",-7,4.09031,281.21758,Onshore,4.66523,2,-7,47.46199,CLEAR 6 | 2022-05-11 19:00:00,-7,3,4,2,False,Waist to chest,3.0,4.0,"[{'height': 5.96053, 'period': 9, 'direction': 301.50568, 'directionMin': 291.25786, 'optimalScore': 0}, {'height': 1.61959, 'period': 13, 'direction': 201.64026, 'directionMin': 196.010805, 'optimalScore': 0}, {'height': 0.46575, 'period': 11, 'direction': 162.17615, 'directionMin': 158.773615, 'optimalScore': 0}, {'height': 0, 'period': 0, 'direction': 0, 'directionMin': 0, 'optimalScore': 0}, {'height': 0.4939, 'period': 15, 'direction': 286.68591, 'directionMin': 282.90094999999997, 'optimalScore': 0}, {'height': 0.20512, 'period': 16, 'direction': 233.78076, 'directionMin': 231.22822499999998, 'optimalScore': 0}]",-7,14.08923,296.92239,Onshore,17.88338,0,-7,48.97399,CLEAR 7 | 2022-05-11 22:00:00,-7,3,4,2,False,Waist to chest,3.0,4.0,"[{'height': 6.41096, 'period': 9, 'direction': 304.17798, 'directionMin': 293.099495, 'optimalScore': 0}, {'height': 1.56847, 'period': 13, 'direction': 200.98853, 'directionMin': 195.473165, 'optimalScore': 0}, {'height': 0.43786, 'period': 11, 'direction': 162.18292, 'directionMin': 158.72098, 'optimalScore': 0}, {'height': 0, 'period': 0, 'direction': 0, 'directionMin': 0, 'optimalScore': 0}, {'height': 0.49478, 'period': 15, 'direction': 286.41113, 'directionMin': 282.741065, 'optimalScore': 0}, {'height': 0.22703, 'period': 16, 'direction': 233.96613, 'directionMin': 231.31288, 'optimalScore': 0}]",-7,19.22053,304.648,Onshore,26.82506,0,-7,49.29797,CLEAR 8 | 2022-05-12 01:00:00,-7,3,4,2,False,Waist to chest,3.0,4.0,"[{'height': 6.73481, 'period': 9, 'direction': 306.15045, 'directionMin': 294.392065, 'optimalScore': 0}, {'height': 1.45656, 'period': 13, 'direction': 199.93683, 'directionMin': 194.57262999999998, 'optimalScore': 0}, {'height': 0.41066, 'period': 11, 'direction': 162.2345, 'directionMin': 158.72821, 'optimalScore': 0}, {'height': 0.41171, 'period': 18, 'direction': 205.45667, 'directionMin': 200.92344, 'optimalScore': 0}, {'height': 0.42782, 'period': 15, 'direction': 285.66772, 'directionMin': 281.988205, 'optimalScore': 0}, {'height': 0.21837, 'period': 16, 'direction': 234.21588, 'directionMin': 231.396045, 'optimalScore': 0}]",-7,19.86497,308.08838,Onshore,29.15768,0,-7,49.51402,CLEAR 9 | 2022-05-12 04:00:00,-7,3,4,2,True,Waist to shoulder,3.091205,4.066929999999999,"[{'height': 6.6271, 'period': 7, 'direction': 307.7179, 'directionMin': 295.63340999999997, 'optimalScore': 0}, {'height': 1.3435, 'period': 13, 'direction': 200.63428, 'directionMin': 195.42863, 'optimalScore': 0}, {'height': 0.38097, 'period': 11, 'direction': 161.66565, 'directionMin': 158.446305, 'optimalScore': 0}, {'height': 0.59278, 'period': 18, 'direction': 196.81744, 'directionMin': 191.00425, 'optimalScore': 0}, {'height': 0.47575, 'period': 14, 'direction': 287.34473, 'directionMin': 283.49074, 'optimalScore': 0}, {'height': 0.19219, 'period': 16, 'direction': 234.17261, 'directionMin': 231.41558, 'optimalScore': 0}]",-7,16.45581,309.3965,Onshore,24.10368,0,-7,48.43401,NIGHT_CLEAR 10 | -------------------------------------------------------------------------------- /raw_data/surf-report.csv: -------------------------------------------------------------------------------- 1 | timestamp,utcOffset,surf_min,surf_max,surf_optimalScore,surf_plus,surf_humanRelation,surf_raw_min,surf_raw_max,swells,utcOffset,speed,direction,directionType,gust,optimalScore,utcOffset,temperature,condition 2 | 2022-05-11 07:00:00,-7,4,5,2,False,Chest to head,4.33071,4.85564,"[{'height': 6.40682, 'period': 10, 'direction': 302.83807, 'directionMin': 291.112125, 'optimalScore': 0}, {'height': 1.62536, 'period': 13, 'direction': 201.36981, 'directionMin': 196.205475, 'optimalScore': 0}, {'height': 0.59469, 'period': 11, 'direction': 163.30792, 'directionMin': 159.731445, 'optimalScore': 0}, {'height': 0.31804, 'period': 19, 'direction': 210.29364, 'directionMin': 204.388295, 'optimalScore': 0}, {'height': 0.26119, 'period': 16, 'direction': 284.66412, 'directionMin': 280.69032000000004, 'optimalScore': 0}, {'height': 0, 'period': 0, 'direction': 0, 'directionMin': 0, 'optimalScore': 0}]",-7,10.02494,320.11238,Onshore,16.71707,0,-7,49.496,NIGHT_CLEAR 3 | 2022-05-11 10:00:00,-7,4,6,2,False,Chest to overhead,3.977035,5.042649999999999,"[{'height': 6.25801, 'period': 10, 'direction': 302.98505, 'directionMin': 291.103995, 'optimalScore': 0}, {'height': 1.57992, 'period': 13, 'direction': 201.73944, 'directionMin': 196.45204, 'optimalScore': 0}, {'height': 0.56624, 'period': 11, 'direction': 163.09503, 'directionMin': 159.51381, 'optimalScore': 0}, {'height': 0.38123, 'period': 19, 'direction': 210.2182, 'directionMin': 203.17021499999998, 'optimalScore': 0}, {'height': 0.28176, 'period': 16, 'direction': 284.17993, 'directionMin': 280.31114, 'optimalScore': 0}, {'height': 0, 'period': 0, 'direction': 0, 'directionMin': 0, 'optimalScore': 0}]",-7,10.48745,320.91681,Onshore,16.52268,0,-7,48.84797,NIGHT_CLEAR 4 | 2022-05-11 13:00:00,-7,3,5,2,False,Waist to head,3.0,5.0,"[{'height': 6.03179, 'period': 9, 'direction': 300.87885, 'directionMin': 289.87952, 'optimalScore': 0}, {'height': 1.61673, 'period': 13, 'direction': 201.97192, 'directionMin': 196.540445, 'optimalScore': 0}, {'height': 0.52618, 'period': 11, 'direction': 162.40509, 'directionMin': 159.051955, 'optimalScore': 0}, {'height': 0.38743, 'period': 19, 'direction': 212.64392, 'directionMin': 205.424225, 'optimalScore': 0}, {'height': 0.29308, 'period': 16, 'direction': 285.64435, 'directionMin': 281.523575, 'optimalScore': 0}, {'height': 0, 'period': 0, 'direction': 0, 'directionMin': 0, 'optimalScore': 0}]",-7,3.07598,346.22836,Cross-shore,6.2203,2,-7,48.16402,CLEAR 5 | 2022-05-11 16:00:00,-7,3,4,2,True,Waist to shoulder,2.986882,4.006562000000001,"[{'height': 6.01742, 'period': 9, 'direction': 300.63123, 'directionMin': 290.34665, 'optimalScore': 0}, {'height': 1.63707, 'period': 13, 'direction': 201.77783, 'directionMin': 196.226775, 'optimalScore': 0}, {'height': 0.49465, 'period': 11, 'direction': 162.26929, 'directionMin': 158.8711, 'optimalScore': 0}, {'height': 0.30253, 'period': 19, 'direction': 222.07281, 'directionMin': 217.174115, 'optimalScore': 0}, {'height': 0.40453, 'period': 15, 'direction': 286.88751, 'directionMin': 283.067675, 'optimalScore': 0}, {'height': 0, 'period': 0, 'direction': 0, 'directionMin': 0, 'optimalScore': 0}]",-7,4.09031,281.21758,Onshore,4.66523,2,-7,47.46199,CLEAR 6 | 2022-05-11 19:00:00,-7,3,4,2,False,Waist to chest,3.0,4.0,"[{'height': 5.96053, 'period': 9, 'direction': 301.50568, 'directionMin': 291.25786, 'optimalScore': 0}, {'height': 1.61959, 'period': 13, 'direction': 201.64026, 'directionMin': 196.010805, 'optimalScore': 0}, {'height': 0.46575, 'period': 11, 'direction': 162.17615, 'directionMin': 158.773615, 'optimalScore': 0}, {'height': 0, 'period': 0, 'direction': 0, 'directionMin': 0, 'optimalScore': 0}, {'height': 0.4939, 'period': 15, 'direction': 286.68591, 'directionMin': 282.90094999999997, 'optimalScore': 0}, {'height': 0.20512, 'period': 16, 'direction': 233.78076, 'directionMin': 231.22822499999998, 'optimalScore': 0}]",-7,14.08923,296.92239,Onshore,17.88338,0,-7,48.97399,CLEAR 7 | 2022-05-11 22:00:00,-7,3,4,2,False,Waist to chest,3.0,4.0,"[{'height': 6.41096, 'period': 9, 'direction': 304.17798, 'directionMin': 293.099495, 'optimalScore': 0}, {'height': 1.56847, 'period': 13, 'direction': 200.98853, 'directionMin': 195.473165, 'optimalScore': 0}, {'height': 0.43786, 'period': 11, 'direction': 162.18292, 'directionMin': 158.72098, 'optimalScore': 0}, {'height': 0, 'period': 0, 'direction': 0, 'directionMin': 0, 'optimalScore': 0}, {'height': 0.49478, 'period': 15, 'direction': 286.41113, 'directionMin': 282.741065, 'optimalScore': 0}, {'height': 0.22703, 'period': 16, 'direction': 233.96613, 'directionMin': 231.31288, 'optimalScore': 0}]",-7,19.22053,304.648,Onshore,26.82506,0,-7,49.29797,CLEAR 8 | 2022-05-12 01:00:00,-7,3,4,2,False,Waist to chest,3.0,4.0,"[{'height': 6.73481, 'period': 9, 'direction': 306.15045, 'directionMin': 294.392065, 'optimalScore': 0}, {'height': 1.45656, 'period': 13, 'direction': 199.93683, 'directionMin': 194.57262999999998, 'optimalScore': 0}, {'height': 0.41066, 'period': 11, 'direction': 162.2345, 'directionMin': 158.72821, 'optimalScore': 0}, {'height': 0.41171, 'period': 18, 'direction': 205.45667, 'directionMin': 200.92344, 'optimalScore': 0}, {'height': 0.42782, 'period': 15, 'direction': 285.66772, 'directionMin': 281.988205, 'optimalScore': 0}, {'height': 0.21837, 'period': 16, 'direction': 234.21588, 'directionMin': 231.396045, 'optimalScore': 0}]",-7,19.86497,308.08838,Onshore,29.15768,0,-7,49.51402,CLEAR 9 | 2022-05-12 04:00:00,-7,3,4,2,True,Waist to shoulder,3.091205,4.066929999999999,"[{'height': 6.6271, 'period': 7, 'direction': 307.7179, 'directionMin': 295.63340999999997, 'optimalScore': 0}, {'height': 1.3435, 'period': 13, 'direction': 200.63428, 'directionMin': 195.42863, 'optimalScore': 0}, {'height': 0.38097, 'period': 11, 'direction': 161.66565, 'directionMin': 158.446305, 'optimalScore': 0}, {'height': 0.59278, 'period': 18, 'direction': 196.81744, 'directionMin': 191.00425, 'optimalScore': 0}, {'height': 0.47575, 'period': 14, 'direction': 287.34473, 'directionMin': 283.49074, 'optimalScore': 0}, {'height': 0.19219, 'period': 16, 'direction': 234.17261, 'directionMin': 231.41558, 'optimalScore': 0}]",-7,16.45581,309.3965,Onshore,24.10368,0,-7,48.43401,NIGHT_CLEAR 10 | -------------------------------------------------------------------------------- /raw_data/tides.csv: -------------------------------------------------------------------------------- 1 | 2022-05-01 07:00:00,NORMAL,5.61 2 | 2022-05-01 08:00:00,NORMAL,4.9 3 | 2022-05-01 09:00:00,NORMAL,3.65 4 | 2022-05-01 10:00:00,NORMAL,2.13 5 | 2022-05-01 11:00:00,NORMAL,0.72 6 | 2022-05-01 12:00:00,NORMAL,-0.23 7 | 2022-05-01 12:59:17,LOW,-0.56 8 | 2022-05-01 13:00:00,NORMAL,-0.55 9 | 2022-05-01 14:00:00,NORMAL,-0.25 10 | 2022-05-01 15:00:00,NORMAL,0.52 11 | 2022-05-01 16:00:00,NORMAL,1.55 12 | 2022-05-01 17:00:00,NORMAL,2.63 13 | 2022-05-01 18:00:00,NORMAL,3.57 14 | 2022-05-01 19:00:00,NORMAL,4.2 15 | 2022-05-01 19:54:39,HIGH,4.4 16 | 2022-05-01 20:00:00,NORMAL,4.4 17 | 2022-05-01 21:00:00,NORMAL,4.13 18 | 2022-05-01 22:00:00,NORMAL,3.53 19 | 2022-05-01 23:00:00,NORMAL,2.84 20 | 2022-05-02 00:00:00,NORMAL,2.33 21 | 2022-05-02 00:47:55,LOW,2.2 22 | 2022-05-02 01:00:00,NORMAL,2.19 23 | 2022-05-02 02:00:00,NORMAL,2.5 24 | 2022-05-02 03:00:00,NORMAL,3.17 25 | 2022-05-02 04:00:00,NORMAL,4.03 26 | 2022-05-02 05:00:00,NORMAL,4.88 27 | 2022-05-02 06:00:00,NORMAL,5.5 28 | 2022-05-02 06:50:41,HIGH,5.68 29 | 2022-05-02 07:00:00,NORMAL,5.68 30 | -------------------------------------------------------------------------------- /raw_data/waves.csv: -------------------------------------------------------------------------------- 1 | Waves,Winder 2 | 5,10 3 | 15,20 -------------------------------------------------------------------------------- /surfdash.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import plotly.express as px # (version 4.7.0 or higher) 3 | import plotly.graph_objects as go 4 | from dash import Dash, dcc, html, Input, Output # pip install dash (version 2.0.0 or higher) 5 | from datetime import date 6 | import datetime 7 | import psycopg2 8 | import os 9 | 10 | #establishing the connection 11 | conn = psycopg2.connect( 12 | database="storm", user='postgres', password='wavestorm', host='127.0.0.1', port= '5432' 13 | ) 14 | #Creating a cursor object using the cursor() method 15 | cursor = conn.cursor() 16 | 17 | #Executing an MYSQL function using the execute() method 18 | cursor.execute("select version()") 19 | 20 | # Fetch a single row using fetchone() method. 21 | data = cursor.fetchone() 22 | print("Connection established to: ",data) 23 | 24 | command = ("""select * from surf_report;""") 25 | 26 | print(command) 27 | cursor.execute(command) 28 | conn.commit() 29 | 30 | surf_report = cursor.fetchall() 31 | conn.commit() 32 | 33 | columns = ['timestamp' , 34 | 'surf_min' , 35 | 'surf_max' , 36 | 'surf_optimalScore' , 37 | 'surf_plus' , 38 | 'surf_humanRelation' , 39 | 'surf_raw_min' , 40 | 'surf_raw_max' , 41 | 'speed', 42 | 'direction', 43 | 'directionType', 44 | 'gust', 45 | 'optimalScore', 46 | 'temperature', 47 | 'condition'] 48 | 49 | surf = pd.DataFrame(surf_report,columns=columns) 50 | 51 | print(surf.head()) 52 | 53 | command = ("""select * from tides;""") 54 | 55 | print(command) 56 | cursor.execute(command) 57 | conn.commit() 58 | 59 | tides = cursor.fetchall() 60 | conn.commit() 61 | 62 | columns_tide = ['timestamp' ,'tide','height'] 63 | 64 | tides = pd.DataFrame(tides,columns=columns_tide) 65 | 66 | app = Dash(__name__) 67 | 68 | # -- Import and clean data (importing csv into pandas) 69 | # df = pd.read_csv("intro_bees.csv") 70 | #df = pd.read_csv("https://raw.githubusercontent.com/Coding-with-Adam/Dash-by-Plotly/master/Other/Dash_Introduction/intro_bees.csv") 71 | 72 | #df = df.groupby(['State', 'ANSI', 'Affected by', 'Year', 'state_code'])[['Pct of Colonies Impacted']].mean() 73 | #df.reset_index(inplace=True) 74 | print(surf[:5]) 75 | today = date.today() 76 | date_list = [today - datetime.timedelta(days=x) for x in range(8)] 77 | # ------------------------------------------------------------------------------ 78 | # App layout 79 | app.layout = html.Div([ 80 | 81 | html.H1("Ocean Beach Surf Report", style={'text-align': 'center'}), 82 | 83 | 84 | dcc.Dropdown(id="slct_year", 85 | options=[ 86 | {"label": date_list[0], "value": date_list[0]}, 87 | {"label": date_list[1], "value": date_list[1]}, 88 | {"label": date_list[2], "value": date_list[2]}, 89 | {"label": date_list[3], "value": date_list[3]}, 90 | {"label": date_list[3], "value": date_list[3]}, 91 | {"label": date_list[4], "value": date_list[4]}, 92 | {"label": date_list[5], "value": date_list[5]}, 93 | {"label": date_list[6], "value": date_list[6]}, 94 | {"label": date_list[7], "value": date_list[7]}], 95 | multi=False, 96 | value=date_list[0], 97 | style={'width': "40%"} 98 | ), 99 | 100 | html.Div(id='output_container', children=[]), 101 | html.Br(), 102 | 103 | dcc.Graph(id='my_bee_map', figure={}), 104 | dcc.Graph(id='my_bee_map2', figure={}), 105 | dcc.Graph(id='my_bee_map3', figure={}) 106 | 107 | ]) 108 | 109 | 110 | # ------------------------------------------------------------------------------ 111 | # Connect the Plotly graphs with Dash Components 112 | @app.callback( 113 | [Output(component_id='output_container', component_property='children'), 114 | Output(component_id='my_bee_map', component_property='figure'), 115 | Output(component_id='my_bee_map2', component_property='figure'), 116 | Output(component_id='my_bee_map3', component_property='figure'),], 117 | [Input(component_id='slct_year', component_property='value')] 118 | ) 119 | def update_graph(option_slctd): 120 | print(option_slctd) 121 | print(type(option_slctd)) 122 | 123 | if option_slctd is None: 124 | container = "No Date selected" 125 | option_slctd = str(date_list[0]) 126 | container = "Date selected: {}".format(option_slctd) 127 | else: 128 | container = "Date selected: {}".format(option_slctd) 129 | 130 | 131 | print(type(option_slctd)) 132 | surff = surf.copy() 133 | surff = surff[(surff["timestamp"] > datetime.datetime.strptime(option_slctd, "%Y-%m-%d")) & (surff["timestamp"] <= datetime.datetime.strptime(option_slctd, "%Y-%m-%d")+ datetime.timedelta(days=1))] 134 | tidess = tides.copy() 135 | tidess = tidess[(tidess["timestamp"] > datetime.datetime.strptime(option_slctd, "%Y-%m-%d")) & (tidess["timestamp"] <= datetime.datetime.strptime(option_slctd, "%Y-%m-%d")+ datetime.timedelta(days=1))] 136 | # Plotly Express 137 | fig = px.bar(surff, x='timestamp', y=['surf_min','surf_max'], text="surf_humanRelation",color_discrete_map={ 138 | "surf_min": "lightsalmon", 139 | "surf_max": "indianred"},barmode='group') 140 | 141 | fig2 = px.scatter(surff, x="timestamp", y="speed",text="directionType",hover_data=["direction","timestamp","speed"]) 142 | fig2.update_traces(textposition='top center') 143 | #fig2.add_annotation(text="directionType", x="timestamp", y="speed", arrowhead=1, showarrow=True,ax="direction") 144 | #fig2.update_layout() 145 | fig2.update_traces(marker=dict(size=12, 146 | line=dict(width=2, 147 | color='DarkSlateGrey')), 148 | marker_colorbar_tickangle = 110, 149 | marker_symbol='arrow-right', 150 | selector=dict(mode='markers')) 151 | fig3 = px.line(tidess,x="timestamp",y="height",text="tide") 152 | fig3.update_traces(textposition='top center') 153 | return container, fig, fig2, fig3 154 | 155 | 156 | # ------------------------------------------------------------------------------ 157 | if __name__ == '__main__': 158 | app.run_server(debug=True) --------------------------------------------------------------------------------