diff --git a/2_process.yml b/2_process.yml index 35186b5..f8ddbf1 100644 --- a/2_process.yml +++ b/2_process.yml @@ -20,6 +20,7 @@ targets: - 2_process/out/gw_daily_quantiles.csv - 2_process/out/gw_data_anomalies.csv - gw_sites_sf + - public/gw-conditions-time-labels.csv # Sites that use "depth below" as their gw level need to be inversed. In the # current implementation, this means any site that used pcode == '72019' @@ -48,8 +49,12 @@ targets: # Prepare data for peaks SVG gw_anomaly_data: - command: read_csv("2_process/out/gw_data_anomalies.csv") + command: read_csv("2_process/out/gw_data_anomalies.csv", col_types = I('cDnnc')) + gw_time: + command: generate_time(gw_anomaly_data) + public/gw-conditions-time-labels.csv: + command: generate_months(target_name, gw_time) gw_anomaly_data_w_paths: command: add_paths_to_data(gw_anomaly_data) gw_anomaly_data_w_colors: - command: add_colors_to_data(gw_anomaly_data_w_paths, scico_palette) + command: add_colors_to_data(gw_anomaly_data_w_paths, scico_palette, gw_time) diff --git a/2_process/src/prep_data_for_visualizing.R b/2_process/src/prep_data_for_visualizing.R index c12ee14..9775726 100644 --- a/2_process/src/prep_data_for_visualizing.R +++ b/2_process/src/prep_data_for_visualizing.R @@ -3,16 +3,20 @@ add_paths_to_data <- function(data_in) { data_in %>% mutate(path = build_path_peak(daily_quant)) } -add_colors_to_data <- function(data_in, scico_palette_nm = "roma") { +add_colors_to_data <- function(data_in, scico_palette_nm = "roma", gw_time) { # Create 5 color palette col_palette <- rev(scico::scico(5, palette = scico_palette_nm)) - data_in %>% - # Add number 1:365 for day of water year for each site - group_by(site_no) %>% - arrange(Date) %>% - mutate(wyday = row_number()) %>% - ungroup() %>% + date_full <- data_in %>% + # create a row for every date x site + expand(Date, site_no) %>% + distinct() + + # bind data to full date sequence so data are complete for each site even with missing data + # this prevents misalignment of dates and animation sequence in later steps + gw_time %>% + left_join(date_full) %>% + left_join(data_in) %>% # Add color based on quantile category mutate(color = ifelse( quant_category == "Very high", @@ -26,3 +30,31 @@ add_colors_to_data <- function(data_in, scico_palette_nm = "roma") { quant_category == "Very low", yes = col_palette[5], no = "black")))))) } +generate_time <- function(data_in) { + date_start <- min(data_in$Date) + date_end <- max(data_in$Date) + + time_df <- tibble(Date = seq.Date(from = date_start, + to = date_end, + by = "1 day")) %>% + mutate(day_seq = dplyr::row_number(.)) + + return(time_df) + +} +generate_months <- function(file_out, data_in){ + # create data to drive annotations on the timeline + # this includes month and year labels + data_in %>% + mutate(month = lubridate::month(Date), + month_label = lubridate::month(Date, label = TRUE), + year = lubridate::year(Date)) %>% + group_by(month, month_label, year) %>% + # draw month labels to the first day of the month + filter(day_seq == min(day_seq)) %>% + ungroup() %>% + group_by(year) %>% + # label years on first month they appear + mutate(year_label = ifelse(day_seq == min(day_seq), year, NA)) %>% + write_csv(file_out) +} \ No newline at end of file diff --git a/3_visualize.yml b/3_visualize.yml index c718a2d..8b2fda2 100644 --- a/3_visualize.yml +++ b/3_visualize.yml @@ -19,37 +19,32 @@ targets: 3_visualize: depends: - - 3_visualize/out/anomaly_peaks.svg - - src/assets/anomaly_peaks.svg - - public/gw_sites.csv - - public/date_peaks.csv + - src/assets/anomaly_peaks.svg + - public/gw-conditions-site-coords.csv ## this is in aws as gw-conditions-sites.csv + - public/gw-conditions-wy20.csv ## in aws as gw-conditions-wy20.csv + - public/gw-conditions-daily-proportions.csv ## in aws as gw-conditions-daily-count.csv - 3_visualize/out/anomaly_peaks.svg: + # exports that are leveraged by vue + # TODO: send to s3 + src/assets/anomaly_peaks.svg: command: build_peaks_svg( target_name, - data_in = gw_anomaly_data_w_colors, - sites_sf = gw_sites_sf, svg_width = svg_width, svg_height = svg_height) - src/assets/anomaly_peaks.svg: - command: file.copy( - to = target_name, - from = '3_visualize/out/anomaly_peaks.svg', - overwrite = TRUE) - - public/date_peaks.csv: + public/gw-conditions-wy20.csv: command: gwl_to_peak( target_name, gw_anomaly_data_w_colors) - public/gw_sites.csv: + public/gw-conditions-site-coords.csv: command: get_site_coords( target_name, sites_sf = gw_sites_sf) - - public/gwl_daily_count.csv: + + public/gw-conditions-daily-proportions.csv: command: site_prop_timeseries(target_name, gw_anomaly_data_w_colors) + diff --git a/3_visualize/src/build_peaks_svg.R b/3_visualize/src/build_peaks_svg.R index e20e455..8d6004e 100644 --- a/3_visualize/src/build_peaks_svg.R +++ b/3_visualize/src/build_peaks_svg.R @@ -1,37 +1,9 @@ -build_peaks_svg <- function(out_file, data_in, sites_sf, svg_width, svg_height) { +build_peaks_svg <- function(out_file, svg_width, svg_height) { svg_root <- init_svg(viewbox_dims = c(0, 0, svg_width=svg_width, svg_height=svg_height)) add_background_map(svg_root, svg_width = svg_width, outline_states = FALSE) - # Add spark line within group per site - - # TODO: THIS ONLY WORKS FOR A SINGLE DAY RIGHT NOW. I CHOSE OCT 31, 2019 - # TODO: Blanket removing any path with an NA in it. There is a smarter way - # to do this but will come back to that. - data_in <- data_in %>% - filter(Date == as.Date("2019-10-31")) %>% - filter(!grepl("NA", path)) - - sites <- sites_sf %>% pull(site_no) %>% unique - for(s in sites) { - site_coords_svg <- sites_sf %>% - filter(site_no == s) %>% - convert_coords_to_svg(svg_width = svg_width, view_bbox = st_bbox(generate_usa_map_data())) - - gw_data_s <- data_in %>% filter(site_no == s) - - # Spark lines centered at GW location - svg_root %>% - add_grp(grp_nm = s, trans_x = site_coords_svg$x, - trans_y = site_coords_svg$y) %>% - xml_add_child("path", - class = sprintf('gwl_%s', s), - style = sprintf("stroke: none; fill: %s; fill-opacity: 50%%", gw_data_s$color), - d = gw_data_s$path) - - } - xml2::write_xml(svg_root, file = out_file) } diff --git a/3_visualize/src/svg_utils_general.R b/3_visualize/src/svg_utils_general.R index 0ec1fb6..0456b2e 100644 --- a/3_visualize/src/svg_utils_general.R +++ b/3_visualize/src/svg_utils_general.R @@ -13,3 +13,4 @@ add_grp <- function(svg_root, grp_nm, trans_x, trans_y) { xml_add_child(svg_root, 'g', id = grp_nm, transform = sprintf("translate(%s %s) scale(0.35, 0.35)", trans_x, trans_y)) } + diff --git a/3_visualize/src/svg_utils_mapping.R b/3_visualize/src/svg_utils_mapping.R index 156e342..4544d30 100644 --- a/3_visualize/src/svg_utils_mapping.R +++ b/3_visualize/src/svg_utils_mapping.R @@ -5,14 +5,19 @@ add_background_map <- function(svg, svg_width, outline_states) { map_data <- generate_usa_map_data(outline_states = outline_states) - bkgrd_grp <- xml_add_child(svg, 'g', id = "bkgrd-map-grp") + bkgrd_grp <- xml_add_child(svg, 'g', + id = "bkgrd-map-grp", + class='map-bkgrd', + style="stroke:white;stroke-width:0.2;fill:white") purrr::map(map_data$ID, function(polygon_id, map_data, svg_width) { d <- map_data %>% filter(ID == polygon_id) %>% convert_coords_to_svg(view_bbox = st_bbox(map_data), svg_width) %>% build_path(connect = TRUE) - # "#e8d9c5" tan (what about slate grey #9fabb7) - xml_add_child(bkgrd_grp, 'path', d = d, class='map-bkgrd', style="stroke:#9fabb7;stroke-width:0.5;fill:none") + xml_add_child(bkgrd_grp, 'path', + d = d, + class='map-bkgrd', + style="stroke:white;stroke-width:0.2;fill:white") }, map_data, svg_width) } diff --git a/3_visualize/src/svg_utils_vue.R b/3_visualize/src/svg_utils_vue.R index a4389b7..09e112e 100644 --- a/3_visualize/src/svg_utils_vue.R +++ b/3_visualize/src/svg_utils_vue.R @@ -1,13 +1,13 @@ gwl_to_peak <- function(file_out, gw_anomaly_data_w_colors){ # Create timeseries data to draw peak animation with D3 - gw_anomaly_data_w_colors %>% + gw_anomaly_data_w_colors %>% mutate(quant = str_replace(quant_category, " ", ""), path_y = round(50-daily_quant, digits = 0)) %>% - filter(!is.na(quant)) %>% + #filter(!is.na(quant)) %>% mutate(site_no = paste0('gwl_', site_no)) %>% - select(site_no, wyday, daily_quant, path_y) %>% - dcast(wyday~site_no, value.var = 'path_y') %>% - arrange(wyday) %>% + select(site_no, day_seq, daily_quant, path_y) %>% + dcast(day_seq~site_no, value.var = 'path_y') %>% + arrange(day_seq) %>% write_csv(file_out) } @@ -20,25 +20,26 @@ get_site_coords <- function(file_out, sites_sf){ } site_prop_timeseries <- function(file_out, gw_anomaly_data_w_colors){ - gw <- gw_anomaly_data_w_colors %>% - filter(!is.na(quant_category)) %>% - group_by(Date) %>% - filter(wyday == max(wyday)) - - ## write json with timeseries of sites in each category - gw %>% - group_by(Date, wyday, quant_category) %>% + + ## write timeseries % of sites in each category + gw_anomaly_data_w_colors %>% + filter(!is.na(quant_category)) %>% # filtering out dates with no category + group_by(Date, day_seq, quant_category) %>% summarize(n_sites = length(unique(site_no))) %>% - left_join(gw %>% - group_by(Date, wyday) %>% + # join with the total number of sites WITH DATA for each day + left_join(gw_anomaly_data_w_colors %>% + filter(!is.na(quant_category))%>% + group_by(Date, day_seq) %>% summarize(n_sites_total = length(unique(site_no)))) %>% mutate(perc = n_sites/n_sites_total) %>% ungroup() %>% mutate(cat = gsub(" ", "", quant_category), perc = round(perc, 3)) %>% select(-quant_category) %>% - reshape2::dcast(Date+wyday+n_sites_total~cat, value.var = "perc") %>% - arrange(wyday) %>% + reshape2::dcast(Date+day_seq+n_sites_total~cat, value.var = "perc") %>% + arrange(day_seq) %>% write_csv(file_out) -} \ No newline at end of file +} + + diff --git a/public/index.html b/public/index.html index 66f7994..a3d7feb 100644 --- a/public/index.html +++ b/public/index.html @@ -1,6 +1,15 @@ + + + @@ -11,18 +20,18 @@ <%= VUE_APP_TITLE %> - + - + - + diff --git a/public/quant_peaks.csv b/public/quant_peaks.csv index 186cfd7..135dfc3 100644 --- a/public/quant_peaks.csv +++ b/public/quant_peaks.csv @@ -1,7 +1,6 @@ -color,peak_mid,quant,path_quant -#7E1900,5,Verylow,M-10 0 C -10 0 0 45 10 0 Z -#C1A53A,17.5,Low,M-10 0 C -10 0 0 32 10 0 Z -#8fce83,50,Normal,M-10 0 C -10 0 0 15 10 0 Z -#8fce83,50,Normal,M-10 0 C -10 0 0 -15 10 0 Z -#479BC5,82.5,High,M-10 0 C -10 0 0 -32 10 0 Z -#1A3399,95,Veryhigh,M-10 0 C -10 0 0 -45 10 0 Z +peak_mid,quant,path_quant +5,Verylow,M-10 0 C -10 0 0 45 10 0 Z +17.5,Low,M-10 0 C -10 0 0 32 10 0 Z +50,Normal,M-10 0 C -10 0 0 15 10 0 C 10 0 0 -15 -10 0 Z +82.5,High,M-10 0 C -10 0 0 -32 10 0 Z +95,Veryhigh,M-10 0 C -10 0 0 -45 10 0 Z diff --git a/src/App.vue b/src/App.vue index 87a4478..01ba384 100644 --- a/src/App.vue +++ b/src/App.vue @@ -63,20 +63,24 @@ // Fonts @import url('https://fonts.googleapis.com/css2?family=Cairo:wght@200;300;400;600;700;900&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Assistant:wght@200;300;400;500;600;700;800&display=swap'); -@import url('https://fonts.googleapis.com/css2?family=Abel&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Noto+Serif:ital,wght@0,400;0,700;1,400;1,700&display=swap'); @import url("https://use.typekit.net/yww2frw.css"); +@import url('https://fonts.googleapis.com/css2?family=Copse&display=swap'); // sort of old timey +@import url('https://fonts.googleapis.com/css2?family=Titillium+Web:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,300;1,400;1,500;1,600;1,700;1,800&display=swap'); -$Abel: 'Abel', sans-serif; $Cairo: 'Cairo', sans-serif; $Assistant: 'Assistant', sans-serif; $Noto: 'Noto Serif', serif; $acu_bold: 'acumin-pro', sans-serif; +$copse: 'Copse', serif; +$titillium: 'Titillium Wed', sans-serif; +$open_sans: 'Open Sans', sans-serif; html, body { height:100%; - background-color: rgb(237, 237, 237); + background-color: rgb(227, 227, 227); margin: 0; padding: 0; line-height: 1.2; @@ -88,26 +92,28 @@ body { @media screen and (max-width: 600px) { font-size: 16px; } + .axis_label { + font-size: 20px; + font-weight: 600; + } } h1{ font-size: 3.5em; - font-weight: 300; + font-weight: 600; font-family: $Assistant; line-height: 1; text-align: left; - text-shadow: 1px 1px 100px rgba(0,0,0,.8); @media screen and (max-width: 600px) { font-size: 2.5em; } } h2{ - font-weight: 400; + font-weight: 600; text-align: left; font-family:$Assistant; font-size: 3em; margin-top: 5px; line-height: 1; - text-shadow: 10px 10px 100px rgba(20, 20, 20, 0.8); @media screen and (max-width: 600px) { font-size: 2em; } @@ -124,7 +130,9 @@ h3{ } p, text { padding: 1em 0 0 0; - font-family: $Assistant; + font-family: $open_sans; + font-weight: 400; + line-height: 1.3; } diff --git a/src/assets/anomaly_peaks.svg b/src/assets/anomaly_peaks.svg index ff7595e..caec7d8 100644 --- a/src/assets/anomaly_peaks.svg +++ b/src/assets/anomaly_peaks.svg @@ -1,6684 +1,15 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + diff --git a/src/assets/usgsHeaderAndFooter/images/usgsLogo.svg b/src/assets/usgsHeaderAndFooter/images/usgsLogo.svg index 965b9a8..206e27c 100644 --- a/src/assets/usgsHeaderAndFooter/images/usgsLogo.svg +++ b/src/assets/usgsHeaderAndFooter/images/usgsLogo.svg @@ -1,3 +1,6 @@ -
-
+ +
@@ -50,49 +51,45 @@ export default { width: null, height: null, margin: { top: 50, right: 0, bottom: 50, left: 0 }, + mar: 50, // data mgmt quant_peaks: null, date_peaks: null, svg: null, - peaky: null, site_coords: null, site_count: null, + time_labels: null, percData: null, days: null, peak_grp: null, - day_length: 50, // frame duration in milliseconds + day_length: 10, // frame duration in milliseconds current_time: 0, start: 0, - n_days: 367, - //x: 0, + n_days: null, sites_list: null, //t2: null, + isPlaying: null, // style for timeline button_color: "grey", button_hilite: "black", - green: "rgb(143, 206, 131)", - - - // TODO: derive from pipeline. inputs are months, day sequence nested w/ key - // timeline data - months: [ - { 'name': 'Jan', 'day': '1' }, - { 'name': 'Feb', 'day': '31' }, - { 'name': 'Mar', 'day': '60' }, - { 'name': 'Apr', 'day': '90' }, - { 'name': 'May', 'day': '120' }, - { 'name': 'Jun', 'day': '150' }, - { 'name': 'Jul', 'day': '181' }, - { 'name': 'Aug', 'day': '210' }, - { 'name': 'Sept', 'day': '240' }, - { 'name': 'Oct', 'day': '270' }, - { 'name': 'Nov', 'day': '300' }, - { 'name': 'Dec', 'day': '331' }, - { 'name': 'Jan', 'day': '367' } - ], + + // scales + dates: null, + xScale: null, + yScale: null, + line: null, + //gwl_color: null, + + // Blue-Brown + verylow: "#BF6200", + low: "#FEB100", + normal: "#B3B3B3", + high: "#2E9EC6", + veryhigh: "#28648A", + pal_BuBr: null, } }, @@ -102,18 +99,11 @@ export default { // resize this.width = window.innerWidth - this.margin.left - this.margin.right; this.height = window.innerHeight*.5 - this.margin.top - this.margin.bottom; - + this.pal_BuBr = [this.veryhigh, this.high, this.normal, this.low, this.verylow]; + // read in data this.loadData(); - // define style before page fully loads - this.svg = this.d3.select("svg.map") - - this.svg.selectAll(".map-bkgrd") - .style("stroke", "white") - .style("stroke-width", "0.1px") - .style("fill", "white") - }, methods:{ loadData() { @@ -121,68 +111,100 @@ export default { // read in data let promises = [ self.d3.csv(self.publicPath + "quant_peaks.csv", this.d3.autotype), // used to draw legend shapes - color palette needs to be pulled out - self.d3.csv("https://labs.waterdata.usgs.gov/visualizations/data/gw-conditions-wy20.csv", this.d3.autotype), - self.d3.csv("https://labs.waterdata.usgs.gov/visualizations/data/gw-conditions-sites.csv", this.d3.autotype), - self.d3.csv("https://labs.waterdata.usgs.gov/visualizations/data/gw-conditions-daily-count.csv", this.d3.autotype) + self.d3.csv("https://labs.waterdata.usgs.gov/visualizations/data/gw-conditions-wy2020.csv", this.d3.autotype), + self.d3.csv("https://labs.waterdata.usgs.gov/visualizations/data/gw-conditions-site-coords.csv", this.d3.autotype), + self.d3.csv("https://labs.waterdata.usgs.gov/visualizations/data/gw-conditions-daily-proportions.csv", this.d3.autotype), + self.d3.csv("https://labs.waterdata.usgs.gov/visualizations/data/gw-conditions-time-labels.csv", this.d3.autotype), ]; Promise.all(promises).then(self.callback); // once it's loaded }, callback(data) { // assign data - this.quant_peaks = data[0]; // peak shapes for legend - this.date_peaks = data[1]; // gwl timeseries data - this.site_coords = data[2]; // site positioning on svg - this.site_count = data[3]; // number of sites x quant_category x wyday - // water days - var wyday = this.date_peaks.columns - wyday.shift(); + // builds legend, has row for each category (2 for normal) + this.quant_peaks = data[0]; + + // gwl site level timeseries data to make peak animation + // row for each day/frame, col for each site + // first column is the day, subsequent are named "gwl_" + site_no + // site values are svg scaled svg positions for the animation + this.date_peaks = data[1]; + + // site coordinates for map + // TODO: pre-draw on map and pick up with d3 + // requires duplicating map style in R so looks consistent on load + // need to consider how to handle sites with no data on the first date + // variables: x, y, site_no, aqfr_type + this.site_coords = data[2]; + + // proportion of sites by each category over time + // variables: Date, day_seq (an integer from 1 to the last day), n_sites, and a column for each gwl category + this.site_count = data[3]; // number of sites x quant_category x day_seq - // sites + // annotations for the timeline + // R pipeline pulls out each month and the year for time labels + // TODO: incorporate additional event annotations + var time_labels = data[4]; + + // days in sequence + var day_seq = this.date_peaks.columns + day_seq.shift(); // drop first col with site_no + + // sites this.sites_list = this.site_coords.map(function(d) { return d.site_no }) var n = this.sites_list.length // to create nested array for indexing in animation // site placement on map - this.sites_x = this.site_coords.map(function(d) { return d.x }) - this.sites_y = this.site_coords.map(function(d) { return d.y }) + var sites_x = this.site_coords.map(function(d) { return d.x }) + var sites_y = this.site_coords.map(function(d) { return d.y }) // reorganize - site is the key with gwl for each day of the wy // can be indexed using site key (gwl_#) - and used to bind data to DOM elements - this.peaky = []; - for (i = 1; i < n; i++) { - var key = this.sites_list[i]; - var wyday = this.date_peaks.map(function(d){ return d['wyday']; }); - var gwl = this.date_peaks.map(function(d){ return d[key]; }); - var site_x = this.sites_x[i]; - var site_y = this.sites_y[i]; - this.peaky.push({key: key, wyday: wyday, gwl: gwl, site_x: site_x, site_y: site_y}) - }; - - // set color scale for path fill - this.quant_color = this.d3.scaleThreshold() - .domain([-40, -25, 25, 40]) - .range(["#1A3399","#479BC5",this.green,"#C1A53A","#7E1900"]) // using a slightly darker green - - //same for bar chart data + var peaky = []; + for (i = 1; i < n; i++) { + var key = this.sites_list[i]; + var day_seq = this.date_peaks.map(function(d){ return d['day_seq']; }); + var gwl = this.date_peaks.map(function(d){ return d[key]; }); + var site_x = sites_x[i]; + var site_y = sites_y[i]; + peaky.push({key: key, day_seq: day_seq, gwl: gwl, site_x: site_x, site_y: site_y}) + }; + + // same for timeseries data but indexed by percentile category var quant_cat = [...new Set(this.quant_peaks.map(function(d) { return d.quant}))]; var n_quant = quant_cat.length - this.percData = []; + var percData = []; for (i = 0; i < n_quant; i++) { var key_quant = quant_cat[i]; - var wyday = this.site_count.map(function(d){ return d['wyday']}); + var day_seq = this.site_count.map(function(d){ return d['day_seq']}); var perc = this.site_count.map(function(d){ return d[key_quant]}); - this.percData.push({key_quant: key_quant, wyday: wyday, perc: perc}) + percData.push({key_quant: key_quant, day_seq: day_seq, perc: perc}) }; - this.days = this.site_count.map(function(d) { return d['wyday']}) - - // draw the chart + this.days = this.site_count.map(function(d) { return d['day_seq']}) + this.dates = this.site_count.map(function(d) { return d['Date']}) + this.n_days = this.days.length + + // set up scales + this.setScales(); // axes, color, and line drawing fnu this.makeLegend(); - this.drawFrame1(this.peaky); - // animated time chart - this.drawLine(this.days, this.percData) + // draw the map + var map_svg = this.d3.select("svg.map") + this.drawFrame1(map_svg, peaky); + // animated time chart + var time_container = this.d3.select("#line-container"); + this.drawLine(time_container, percData); + this.addButtons(time_container, time_labels); + + // control animation + this.animateLine(this.start); + this.animateGWL(this.start); + var play_container = this.d3.select("#play-container"); + this.playButton(play_container, "200","50"); + + }, createPanel(month) { var tl = new TimelineMax(); @@ -190,189 +212,307 @@ export default { // add rest of this return tl; }, - drawLine(date_range, prop_data) { + addButtons(time_container, time_labels){ const self = this; + // timeline events/"buttons" + // want these to be buttons that rewind the animation to the date + // and also drive annotations to events + + var button_month = time_container.select('svg') + .append("g") + .attr("transform", "translate(20," + 120 + ")") + .attr("z-index", 100) + + // month points on timeline + button_month.selectAll(".button_inner") + .data(time_labels).enter() + .append("circle") + .attr("class", function(d,i) { return "button_inner inner_" + d.month_label + "_" + d.year } ) + .attr("r", 5) + .attr("cx", function(d) { return self.xScale(d.day_seq) }) + .attr("cy", 0) + .attr("stroke", "white") + .attr("stroke-width", "2px") + .attr("fill", this.button_color) + //.on('click', function(d, i) { + //self.moveTimeline(d); + // }) + /* .on('mouseover', function(d, i) { + self.buttonSelect(d); + }) + .on('mouseout', function(d, i) { + self.buttonDeSelect(d); + }) */ + + // month labels + button_month.selectAll(".button_name") + .data(time_labels) + .enter() + .append("text") + .attr("class", function(d,i) { return "button_name name_" + d.month_label + "_" + d.year } ) + .attr("x", function(d) { return self.xScale(d.day_seq)-10 }) // centering on pt + .attr("y", 25) + .text(function(d, i) { return d.month_label }) + .attr("text-align", "start") + //.on('click', function(d, i) { + //self.moveTimeline(d); + //}) + /* .on('mouseover', function(d, i) { + self.buttonSelect(d); + }) + .on('mouseout', function(d, i) { + self.buttonDeSelect(d); + }) */ + + // filter to just year annotations for first month they appear + var year_labels = time_labels.filter(function(el) { + return el.year_label >= 2000; + }); + + // add year labels to timeline + button_month.selectAll(".button_year") + .data(year_labels) + .enter() + .append("text") + .attr("class", function(d,i) { return "button_year button_" + d.year } ) + .attr("x", function(d) { return self.xScale(d.day_seq)-10 }) // centering on pt + .attr("y", 45) + .text(function(d, i) { return d.year }) - var line_width = this.width; - var line_height = 250; - var mar = 20; - - // set up svg for timeline - var svg = this.d3.select("#line-container") - .append("svg") - .attr("width", "100%") - .attr("preserveAspectRatio", "xMinYMin meet") - .attr("viewBox", "0 0 " + line_width + " " + line_height) - .attr("id", "x-line") - - // scale space - var xScale = this.d3.scaleLinear() - .domain([0, this.n_days]) - .range([mar, line_width-2*mar]) - - // define axes - var xLine = this.d3.axisBottom() - .scale(xScale) - .ticks(0).tickSize(0); - - // draw axes - var liney = svg.append("g") - .call(xLine) - .attr("transform", "translate(0," + 120 + ")") - .classed("liney", true) - - // style axes - liney.select("path.domain") - .attr("id", "timeline-x") - .attr("color", "lightgrey") - .attr("stroke-width", "3px") - - // timeline events/"buttons" - var button_month = svg.append("g") - .classed("#btn-month", true) - .attr("transform", "translate(0," + 120 + ")") - .attr("z-index", 100) - - // month points on timeline - // TODO: make functions that accept any date and label for annotations - button_month.selectAll(".button_inner") - .data(this.months).enter() - .append("circle") - .attr("class", function(d,i) { return "button_inner inner_" + d.name + " " + d.name } ) - .attr("r", 3) - .attr("cx", function(d) { return xScale(d.day) }) - .attr("cy", 0) - .attr("stroke", this.button_color) - .attr("stroke-width", "2px") - .attr("fill", this.button_color) - .on('click', function(d, i) { - //self.moveTimeline(d); // requires gsap for playback control or some way to cancel playback loop partway - }) - .on('mouseover', function(d, i) { - self.buttonSelect(d); - }) - .on('mouseout', function(d, i) { - self.buttonDeSelect(d); - }) - - // month labels - button_month.selectAll(".button_name") - .data(this.months) - .enter() - .append("text") - .attr("class", function(d,i) { return "button_name name_" + d.name + " " + d.name } ) - .attr("x", function(d) { return xScale(d.day)-12 }) // centering on pt - .attr("y", 25) - .attr("stroke", this.button_color) - .text(function(d, i) { return d.name }) - .attr("text-align", "middle") - .on('click', function(d, i) { - //self.moveTimeline(d); - }) - .on('mouseover', function(d, i) { - self.buttonSelect(d); - }) - .on('mouseout', function(d, i) { - self.buttonDeSelect(d); - }) - - // year annotations - // TODO: derive from data input via pipeline - button_month - .append("text") - .attr("class", function(d,i) { return "button_year" } ) - .attr("x", function(d) { return xScale(1)-10 }) // centering on pt - .attr("y", 45) - .attr("stroke", this.button_color) - .text(function(d, i) { return 2021 }) - - button_month - .append("text") - .attr("class", function(d,i) { return "button_year" } ) - .attr("x", function(d) { return xScale(367)-20 }) // centering on pt - .attr("y", 45) - .attr("stroke", this.button_color) - .text(function(d, i) { return 2022 }) - - button_month - .append("text") - .attr("class", function(d,i) { return "axis_label" } ) - .attr("x", function(d) { return xScale(1)-10 }) // centering on pt - .attr("y", -110) - .attr("stroke", this.button_color) - .text(function(d, i) { return "Proportion of wells" }) - - // add line chart - // line chart showing proportion of gages in each category - var line_chart = svg.append("g") - .classed("time-chart", true) - .attr("id", "time-legend") - .attr("transform", "translate(0," + 20 + ")") + // chart title + button_month + .append("text") + .attr("class", function(d,i) { return "axis_label" } ) + .attr("x", function(d) { return self.xScale(1)-40 }) // centering on pt + .attr("y", -120) + .text(function(d, i) { return "Wells by groundwater level" }) - var y = this.d3.scaleLinear() - .domain([0, 0.5]) - .range([100, 0]) - var bar_color = this.d3.scaleOrdinal() - .domain(["Veryhigh", "High", "Normal", "Low","Verylow"]) - .range(["#1A3399","#479BC5",this.green,"#C1A53A","#7E1900"]) + }, + drawLine(time_container, prop_data) { + const self = this; - var line = this.d3.line() - .defined(d => !isNaN(d)) - .x((d, i) => xScale(this.days[i])) - .y(d => y(d)) + var line_height = 250; + var y_nudge = 20; + + // set up svg for timeline + var svg = time_container + .append("svg") + .attr("width", "100%") + .attr("preserveAspectRatio", "xMinYMin meet") + .attr("viewBox", "0 -20 " + this.width + " " + line_height) + .attr("id", "x-line") + + // define axes + var xLine = this.d3.axisBottom() + .scale(this.xScale) + .ticks(0).tickSize(0); + + var yLine = this.d3.axisLeft().tickFormat(this.d3.format('~%')) + .scale(this.yScale) + .ticks(2).tickSize(6); + + // draw axes + var xliney = svg.append("g") + .call(xLine) + .attr("transform", "translate(20," + 120 + ")") + .classed("liney", true) + + var yliney = svg.append("g") + .call(yLine) + .attr("transform", "translate(47," + y_nudge + ")") + .classed("liney", true) + + // style axes + xliney.select("path.domain") + .attr("id", "timeline-x") + .attr("color", "white") + .attr("stroke-width", "4px") + + yliney.select("path.domain") + .attr("id", "timeline-y") + .attr("color", "white") + .attr("stroke-width", "4px") + .attr('font-size', '5rem') + + // add line chart + // line chart showing proportion of gages in each category + var line_chart = svg.append("g") + .classed("time-chart", true) + .attr("id", "time-legend") + .attr("transform", "translate(20," + y_nudge + ")") // add line chart - const path = line_chart.append("g") - .attr("fill", "none") - .attr("stroke-linejoin", "round") - .attr("stroke-linecap", "round") + line_chart.append("g") + .attr("fill", "none") + .attr("stroke-linejoin", "round") + .attr("stroke-linecap", "round") .selectAll("path") .data(prop_data) .join("path") - .attr("d", d => line(d.perc)) - .attr("stroke", function(d) { return bar_color(d.key_quant) }) + .attr("d", d => self.line(d.perc)) + .attr("stroke", function(d) { return self.gwl_color(d.key_quant) }) .attr("stroke-width", "3px") .attr("opacity", 0.7); - // animate line to time - var start = this.start; - - line_chart.append("rect") + // animate line to time + line_chart.append("rect") .data(this.days) .classed("hilite", true) - //.attr("transform", "translate(1, 0)") .attr("width", "5") .attr("height", "100") .attr("opacity", 0.5) .attr("fill", "grey") - .attr("x", xScale(this.days[start])) - - this.animateLine(start); + .attr("x", self.xScale(this.days[this.start])) + + // add date ticker + /* line_chart + .append("text") + .attr("class", "ticker-date") + .attr("x", self.xScale(365)) // centering on pt + .attr("y", -10) + .text( this.dates[this.start]) + .attr("text-anchor", "end") */ + + }, + setScales(){ + + // set color scale for path fill + this.quant_color = this.d3.scaleThreshold() + .domain([-40, -25, 25, 40]) + .range(this.pal_BuBr) + + // x axis of line chart + this.xScale = this.d3.scaleLinear() + .domain([0, this.n_days-1]) + .range([this.mar/2, this.width-2*this.mar]) + + // y axis of line chart + this.yScale = this.d3.scaleLinear() + .domain([0, 0.6]) // this should come from the data - round up from highest proportion value + .range([100, 0]) + + // line drawing + this.line = this.d3.line() + .defined(d => !isNaN(d)) + .x((d, i) => this.xScale(this.days[i])) + .y(d => this.yScale(d)) + + // categorical color scale + this.gwl_color = this.d3.scaleOrdinal() + .domain(["Veryhigh", "High", "Normal", "Low","Verylow"]) + .range(this.pal_BuBr) + + }, + playButton(svg, x, y) { + const self = this; + var svg_play = svg + .append("svg") + .attr("width", x) + .attr("height", y) + .attr("preserveAspectRatio", "xMinYMin meet") + .attr("viewbox", "0 0 " + x + " " + y) + + var button = svg_play.append("g") + .attr("transform", "translate("+ 0 +","+ 0 +")") + .attr("class", "play_button"); + + button + .append("rect") + .attr("width", 100) + .attr("height", 40) + .attr("rx", 4) + .style("color", "black") + .style("fill","transparent") + .attr("stroke-width", "1px") + .classed("pressMe", true); + + svg_play + .append("text") + .text("Play") + .attr("x", 42) + .attr("y", 30) + .attr("font-size", "1.5rem") + .attr("font-weight", "600") + + // append hover title + button + .append("title") + .text("replay") + + button + .append("path") + .attr("d", "M7.5 7.5 L7.5 35 L32.5 20 Z") + .style("fill", "white") + .style("stroke", "black") + .attr("stroke-width", "2px"); + + button.select("rect") + .on("mousedown", function() { + self.pressButton(self.isPlaying) + }); + }, + pressButton(playing) { + const self = this; + + // trigger animation if animation is not already playing + if (playing == false) { + self.animateLine(0); + self.animateGWL(0); + } + }, + resetPlayButton() { + const self = this; + + // reset global playing variable to false now that animation is complete + self.isPlaying = false; + + // undim button + let button_rect = this.d3.selectAll(".play_button").selectAll("rect") + .style("fill", '#9b6adb8e') }, animateLine(start){ // animates grey line on timeseries chart to represent current timepoint - var line_width = this.width; + const self = this; var line_height = 250; - var mar = 20; + + // set indicator for play button + self.isPlaying = true + + // dim play button rectangle + let button_rect = this.d3.selectAll(".play_button").selectAll("rect") + button_rect + .style("fill", "#d6d6d6") - var x = this.d3.scaleLinear() - .domain([1, this.n_days]) - .range([mar, line_width-mar]) if (start < this.n_days){ this.d3.selectAll(".hilite") .transition() .duration(this.day_length) - .attr("x", x(this.days[start])) + .attr("x", self.xScale(this.days[start])) .end() .then(() => this.animateLine(start+1)) + + /* this.d3.selectAll(".ticker-date") + .transition() + .duration(this.day_length) + .text(this.dates[start]) + .end() + .then(() => this.animateLine(start+1)) */ + } else { this.d3.selectAll(".hilite") .transition() .duration(this.day_length) - .attr("x", x(this.days[this.n_days])) + .attr("x", self.xScale(this.days[this.n_days-1])) + + // once animation has completed, reset color of play button + // and set isPlaying to false + button_rect + .transition() + .delay(this.day_length*(this.n_days-1)) + .on("end", self.resetPlayButton); } }, buttonSelect(d){ @@ -387,7 +527,6 @@ export default { .duration(100) .attr("r", 6) .attr("fill", this.button_hilite) - .attr("stroke", this.button_hilite) }, buttonDeSelect(d){ // unhighlight on mouseout @@ -408,10 +547,9 @@ export default { .duration(100) .attr("r", 3) .attr("fill", this.button_color) - .attr("stroke", this.button_color) }, initTime(){ - // TO DO: init nested timelines for playback control + // nested timelines for playback control // broken up by month and tagged for user control var tl_jan = new TimelineMax(); // TimelineMax permits repeating animation var tl_feb = new TimelineMax(); @@ -442,10 +580,15 @@ export default { .add(tl_nov) .add(tl_dec) + }, + animateCharts(){ + // link animation functions to same day + }, makeLegend(){ + const self = this; - var legend_height = 160; + var legend_height = 200; var legend_width = 240; // make a legend @@ -457,116 +600,140 @@ export default { .attr("preserveAspectRatio", "xMinYMin meet") .attr("viewbox", "0 0 " + legend_width + " " + legend_height) .append("g").classed("legend", true) - .attr("transform", "translate(90, 0)") - + .attr("transform", "translate(0, 0)") + + // legend elements var legend_keys = ["Very low", "Low", "Normal", "High","Very high"]; // labels - var shape_dist = [5,25,42,42,60,83,103]; // y positioning (normal has 2 shapes butted together) - var perc_label = ["0 - 0.1", "0.1 - 0.25" ,"0.25 - 0.75", "0.75 - 0.9", "0.9+"] + var perc_label = ["0.00 - 0.10", "0.10 - 0.25" ,"0.25 - 0.75", "0.75 - 0.90", "0.90 - 1.00"] // percentile ranges // draw path shapes and labels + var legend_title_x = 0; + var legend_label_x = 56; legend_peak .append("text") - .text("GWL") - .attr("x", -20) - .attr("y", "30") - .style("font-size", "20") - .style("font-weight", 700) - .attr("text-anchor", "end") + .text("Groundwater levels") + .attr("x", legend_title_x) + .attr("y", "20") + .style("font-size", "1.5rem") + .style("font-weight", 600) // matching css of .axis_labels + .attr("text-anchor", "start") legend_peak .append("text") - .text("Percentile") - .attr("x", 105) - .attr("y", "30") - .style("font-size", "20") - .style("font-weight", 700) - .attr("text-anchor", "end") + .text("Percentile based on historic") + .attr("x", legend_title_x) + .attr("y", "40") + .style("font-size", "1rem") + .style("font-weight", 400) + .attr("text-anchor", "start") + + legend_peak + .append("text") + .text("daily record at each site") + .attr("x", legend_title_x) + .attr("y", "60") + .style("font-size", "1rem") + .style("font-weight", 400) + .attr("text-anchor", "start") + + + var label_end = 180; // moves legend keys and labels up and down + var label_space = 23; // spacing between legend elements + // add glyphs legend_peak.selectAll("peak_symbol") .data(this.quant_peaks) .enter() .append("path") - .attr("fill", function(d){return d["color"]}) + .attr("fill", function(d){return self.gwl_color(d.quant)}) .attr("d", function(d){return d["path_quant"]}) - .attr("transform", function(d, i){return "translate(0, " + (140-shape_dist[i]) + ") scale(.8)"}) + .attr("transform", function(d, i){return "translate(" + 95 + ", " + ((label_end-8)-20*i) + ") scale(.85)"}) .attr("id", function(d){return d["quant"]}) .attr("class", "peak_symbol") - // add categorical labels very low - very high + // add categorical labels ranked from very low to very high legend_peak.selectAll("mylabels") .data(legend_keys) .enter() .append("text") - .attr("x", -20) - .attr("y", function(d,i){ return 140 - (i*22)}) // 100 is where the first dot appears. 25 is the distance between dots + .attr("x", legend_label_x+20) + .attr("y", function(d,i){ return label_end - (i*label_space)}) .text(function(d){ return d}) .attr("text-anchor", "end") .style("alignment-baseline", "middle") + .style("font-weight", "600") + .attr("font-size", "16px") + // label percentile ranges in each category legend_peak.selectAll("percLabels") .data(perc_label) .enter() .append("text") - .attr("x", 20) - .attr("y", function(d,i){ return 140 - (i*22)}) // 100 is where the first dot appears. 25 is the distance between dots + .attr("x", legend_label_x+60) + .attr("y", function(d,i){ return label_end - (i*label_space)}) .text(function(d){ return d}) .attr("text-anchor", "start") .style("alignment-baseline", "middle") + .attr("font-size", "16px") }, - drawFrame1(data){ + drawFrame1(map_svg, data){ // draw the first frame of the animation const self = this; - // select existing paths using class + // select existing paths using class - currently all drawn with D3 + // to grab existing paths, needs to look exact same as first frame + // requries dealing with sites with no data on day 1 var start = this.start; - this.peak_grp = this.svg.selectAll("path.peak") + this.peak_grp = map_svg.selectAll("path.gwl_glyph") .data(data, function(d) { return d ? d.key : this.class; }) // binds data based on class/key .join("path") // match with selection .attr("transform", d => `translate(` + d.site_x + ' ' + d.site_y + `) scale(0.35 0.35)`) - // draws a oath for each site, using the first date + // draws a path for each site, using the first date this.peak_grp .attr("class", function(d) { return d.key }) - .attr("fill", function(d) { return self.quant_color(d.gwl[0]) }) // this is not exactly right - .attr("stroke-width", "1px") + .attr("fill", function(d) { return self.quant_color(d.gwl[start]) }) .attr("opacity", ".5") - .attr("d", function(d) { return "M-10 0 C -10 0 0 " + d.gwl[start]*1 + " 10 0 Z" } ) // d.gwl.# corresponds to day of wy, starting with 0 + .attr("d", function(d) { return "M-10 0 C -10 0 0 " + d.gwl[start] + " 10 0 Z" } ) // d.gwl.# corresponds to day of wy, starting with 0 - this.animateGWL(start); // once sites are drawn, trigger animation + // this.animateGWL(this.start); // once sites are drawn, trigger animation }, animateGWL(start){ const self = this; // animate path d and fill by wy day - if (start < this.n_days ){ - // transition through days in sequence + + if (start < 365 ){ this.peak_grp .transition() .duration(this.day_length) // duration of each day - .attr("d", function(d) { return "M-7 0 C -7 0 0 " + d.gwl[start]*1.5 + " 7 0 Z" }) + .attr("d", function(d) { return "M-10 0 C -10 0 0 " + d.gwl[start] + " 10 0 Z" }) .attr("fill", function(d) { return self.quant_color(d.gwl[start]) }) .end() - .then(() => this.animateGWL(start+1)) // loop animation increasing by 1 wyday + .then(() => this.animateGWL(start+1)) // loop animation increasing by 1 day_seq } else { // if it's the last day of the water year, stop animation this.peak_grp .transition() .duration(this.day_length) // duration of each day - .attr("d", function(d) { return "M-10 0 C -10 0 0 " + d.gwl[this.n_days] + " 10 0 Z" }) + .attr("d", function(d) { return "M-10 0 C -10 0 0 " + d.gwl[365] + " 10 0 Z" }) + .attr("fill", function(d) { return self.quant_color(d.gwl[365]) }) } } } } \ No newline at end of file diff --git a/src/components/PreFooterCodeLinks.vue b/src/components/PreFooterCodeLinks.vue index 409b4a9..dc3eec9 100644 --- a/src/components/PreFooterCodeLinks.vue +++ b/src/components/PreFooterCodeLinks.vue @@ -29,12 +29,12 @@ display: flex; justify-content: right; width: 100%; - background-color: #c2c4c5; + background-color:#323333; margin: 0 auto; padding: 0.4rem; - border-bottom: 2px solid black; + border-bottom: 2px solid #323333; a { - color: #090211; + color: white; margin-left: 10px; font-family: 'Source Sans Pro',sans-serif; text-decoration: none diff --git a/src/components/vue-dropdown-menu.vue b/src/components/vue-dropdown-menu.vue deleted file mode 100644 index bb4cc21..0000000 --- a/src/components/vue-dropdown-menu.vue +++ /dev/null @@ -1,342 +0,0 @@ - - - - - \ No newline at end of file