# Copyright (c) 2018 Kelvin Say # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. library(ggplot2) library(reshape2) library(scales) library(stringr) library(data.table) ################################################################################################################# # Single Day ################################################################################################################# # --------------------------------------------------------------------------------------------------------------- # Use the information from technical_data to generate the energy profile across this particular year and day of # the year with the associated PV and battery capacities # --------------------------------------------------------------------------------------------------------------- vis_Day_EnergyProfile <- function(technical_data, year, day_of_year, pv_capacity, battery_capacity) { # Search for a valid PV and Battery Index pv_index <- 0 battery_index <- 0 for (i in 1:nrow(technical_data$Operational)) { if (pv_capacity == technical_data$Operational[i,1][[1]]$'PV Capacity (Wp)') { pv_index <- i break } } for (j in 1:ncol(technical_data$Operational)) { if (battery_capacity == technical_data$Operational[1,j][[1]]$'Battery Capacity (Wh)') { battery_index <- j break } } # Normal Operation if ((pv_index > 0) & (battery_index > 0) & (day_of_year > 0) & (day_of_year <= 365) & (year <= technical_data$Inputs$Timeline$Info$'Operational Years')) { time_step <- technical_data$Inputs$Timeline$Info$'Time Step (s)' start_index <- (day_of_year-1)*24*3600/time_step +1 end_index <- day_of_year*24*3600/time_step n_samples <- end_index - start_index + 1 timeline <- technical_data$Inputs$Timeline timeline$Data$'Time' <- timeline$Data$'Time'[start_index:end_index] baseload <- technical_data$Inputs$Baseload baseload$Data$'Load (W)' <- baseload$Data$'Load (W)'[start_index:end_index] solar <- technical_data$Inputs$Solar solar$Info$'Nominal Capacity (Wp)' <- pv_capacity solar$Data$'Insolation Reference (W/Wp)' <- solar$Data$'Insolation Reference (W/Wp)'[start_index:end_index] if (pv_capacity > 0) { gradient <- (solar$Info$'25 Year Capacity (%)' - 1) / (25*365) solar_degradation_start <- ((year - 1) * 365 + (day_of_year-1)) * gradient + 1 solar_degradation_end <- ((year - 1) * 365 + day_of_year) * gradient + 1 solar_degradation <- solar_degradation_start + (solar_degradation_end - solar_degradation_start)/n_samples*0:(n_samples-1) solar$Data$'Generation (W)' <- pv_capacity * solar$Data$'Insolation Reference (W/Wp)' * solar_degradation } else { solar$Data$'Generation (W)' <- NULL } battery <- technical_data$Inputs$Battery battery$Info$'Rated Capacity (Wh)' <- battery_capacity if (battery_capacity > 0) { gradient <- (battery$Info$'10 Year Capacity (%)' - 1) / (10*365) capacity_start_Wh <- battery_capacity * (((year - 1) * 365 + (day_of_year-1)) * gradient + 1) capacity_end_Wh <- battery_capacity * (((year - 1) * 365 + day_of_year) * gradient + 1) v_capacity_Wh <- capacity_start_Wh + (capacity_end_Wh - capacity_start_Wh)/n_samples*0:(n_samples-1) battery$Data$'Capacity (Wh)' = v_capacity_Wh battery$Initial$'SoC (Wh)' = technical_data$Operational[pv_index, battery_index][[1]]$Daily$'B.Soc.Initial (Wh)'[(year-1)*365 + day_of_year] } else { battery$Data$'Capacity (Wh)' = NULL battery$Initial$'SoC (Wh)' = 0 } pv_battery_operation <- process_PV_Battery_Load(timeline, baseload, solar, battery) meter_operation <- data.table('Time' = timeline$Data$Time, 'Load (W)' = baseload$Data$'Load (W)', 'PV (W)' = solar$Data$'Generation (W)', 'NetLoad (W)' = pv_battery_operation$'NetLoad (W)', 'PVUsed (W)' = pv_battery_operation$'PVUsed (W)', 'B.Charging (W)' = pv_battery_operation$'B.Charging (W)', 'B.Discharging (W)' = pv_battery_operation$'B.Discharging (W)', 'B.InvLosses (W)' = pv_battery_operation$'B.InvLosses (W)', 'B.CapLosses (Wh)' = pv_battery_operation$'B.CapLosses (Wh)', 'B.SoC (Wh)' = pv_battery_operation$'B.SoC (Wh)', 'Imported (W)' = pv_battery_operation$'Imported (W)', 'Exported (W)' = pv_battery_operation$'Exported (W)') max_x <- max(meter_operation$'PV (W)', meter_operation$'Load (W)') min_x <- min(meter_operation$'NetLoad (W)', 0) max_y <- last(meter_operation$'Time') min_y <- first(meter_operation$'Time') df <- data.frame(Time = meter_operation$'Time', Load = meter_operation$'Load (W)', PV = meter_operation$'PV (W)', NetLoad = meter_operation$'NetLoad (W)', SoC = meter_operation$'B.SoC (Wh)'/battery_capacity * max_x) melt_df <- melt(df, id="Time") plot1 <- ggplot(melt_df, aes(x=Time, y=value, colour=variable, group=variable)) + geom_line() + coord_cartesian(ylim = c(min_x, max_x), xlim = c(min_y,max_y)) + xlab(paste("Year ", year, " Day ", day_of_year, ", ", format(min_y, "%d/%m"),sep="")) + ylab("Power (W)") + scale_x_datetime(breaks = date_breaks("2 hours"), minor_breaks = date_breaks("1 hour"), labels = date_format("%H:%M")) + ggtitle(paste0("Household Electricity Performance [", pv_capacity/1000, "kWp + ", battery_capacity/1000, "kWh]"), subtitle = paste0("Profile: ", technical_data$Inputs$Baseload$Info$'Profile', " @ ", technical_data$Inputs$Baseload$Info$'Annual Consumption (MWh)', " MWh/year")) + theme(legend.title=element_blank()) + theme(plot.title = element_text(hjust = 0.5)) # Error Case: Return an empty plot } else { df <- data.frame() plot1 <- ggplot(df) } return(plot1) } ################################################################################################################# # Single Year ################################################################################################################# # --------------------------------------------------------------------------------------------------------------- # Display the NPV spectrum of every PV and Battery combination and highlight the maximum # --------------------------------------------------------------------------------------------------------------- vis_Annual_NPV <- function(simulation_info, technical_summary, financial_data) { max_found <- FALSE pv_sizes <- technical_summary$'PV Capacity (Wp)' / 1000 battery_sizes <- technical_summary$'Battery Capacity (Wh)' / 1000 x_diff <- simulation_info$'PV Delta (Wp)' / 1000 y_diff <- simulation_info$'Battery Delta (Wh)' / 1000 mat_npv <- financial_data$'System.NPV' colnames(mat_npv) <- paste("batt=", battery_sizes, sep="") rownames(mat_npv) <- paste("pv=", pv_sizes, sep="") # Determine the level of self-sufficiency for the most profitable system v_max <- which((mat_npv==max(mat_npv)) & (mat_npv > 0)) if (length(v_max) > 0) { max_found <- TRUE max_battery_index <- ceiling(v_max[1]/length(pv_sizes)) max_pv_index <- (v_max[1]-1) %% length(pv_sizes) + 1 self_sufficiency <- round(technical_summary$Total$'Self-Sufficiency (%)'[max_pv_index, max_battery_index] * 100, 2) melt_npv <- melt(mat_npv) names(melt_npv) <- c("pv","batt","npv") melt_npv$pv <- as.numeric(str_sub(melt_npv$pv, str_locate(melt_npv$pv, "=")[1,1] + 1)) melt_npv$batt <- as.numeric(str_sub(melt_npv$batt, str_locate(melt_npv$batt, "=")[1,1] + 1)) melt_npv$npv <- sapply(melt_npv$npv, function(x) if (x < 0) -10 else x) } # Error Condition: return an empty plot if (!max_found) { df <- data.frame() plot1 <- ggplot(df) + geom_point() + theme_bw() + xlim(0,max(pv_sizes)) + ylim(0,max(battery_sizes)) + xlab(paste("Solar PV Capacity (kWp) $", round(financial_data$Inputs$Costs$'PV Cost ($/kWp)',0), "/kWp", sep="")) + ylab(paste("Battery Capacity (kWh) $", round(financial_data$Inputs$Costs$'Battery Cost ($/kWh)',0), "/kWh", sep="")) + ggtitle(paste("System NPV ($) Over ", financial_data$Inputs$Finance$'Analysis Years', " Years", sep=""), subtitle=paste("Consumption = ", round(technical_summary$Total$'Load (kWh)'/financial_data$Inputs$Finance$'Analysis Years'/1000,1), " MWh/year, " , "Inverter Limit = ", financial_data$Inputs$Tariff$'FiT System Limit (Wp)' / 1000, "kW\n" , "Retail Price = ", round(financial_data$Inputs$Tariff$'Import ($/kWh)' * 100,3), "c, " , "Feed-in Tariff = ", round(financial_data$Inputs$Tariff$'Export ($/kWh)' * 100,3), "c, " , "Discount Rate = ", financial_data$Inputs$Finance$'Discount Rate (%)' * 100, "%\n" , "Import Inflation = ", financial_data$Inputs$Tariff$'Import Inflation (%)' * 100, "%, " , "Export Inflation = ", financial_data$Inputs$Tariff$'Export Inflation (%)' * 100, "%\n" , "Maximum Profit = none and 0% self-sufficient" , sep="")) # Normal Condition } else { plot1 <- ggplot(melt_npv) + aes(x = pv, y = batt, z = npv, fill = npv) + geom_tile() + coord_equal(0.5) + # stat_contour(aes(fill=..level..), geom="polygon") + #, binwidth=0.05) + geom_contour(color = "white", alpha = 0.5) + scale_fill_distiller(palette="Greens", direction=1, na.value=rgb(0,0,0,0), limits=c(0, max(melt_npv$npv))) + theme_bw() # Add the horizontal and vertical lines to mark the maxima plot1 <- plot1 + annotate("point", x=pv_sizes[max_pv_index], y=battery_sizes[max_battery_index]) + annotate("text", hjust="left", vjust="center", x=pv_sizes[max_pv_index]+x_diff/2, y=battery_sizes[max_battery_index], label=paste("", pv_sizes[max_pv_index], "kWp, ", battery_sizes[max_battery_index], "kWh", sep=""))#, size=3) # Format the labels plot1 <- plot1 + xlab(paste("Solar PV Capacity (kWp) $", round(financial_data$Inputs$Costs$'PV Cost ($/kWp)',0), "/kWp", sep="")) + ylab(paste("Battery Capacity (kWh) $", round(financial_data$Inputs$Costs$'Battery Cost ($/kWh)',0), "/kWh", sep="")) + ggtitle(paste("System NPV ($) Over ", financial_data$Inputs$Finance$'Analysis Years', " Years", sep=""), subtitle=paste("Consumption = ", round(technical_summary$Total$'Load (kWh)'/financial_data$Inputs$Finance$'Analysis Years'/1000,1), " MWh/year, " , "Inverter Limit = ", financial_data$Inputs$Tariff$'FiT System Limit (Wp)' / 1000, "kW\n" , "Retail Price = ", round(financial_data$Inputs$Tariff$'Import ($/kWh)' * 100,3), "c, " , "Feed-in Tariff = ", round(financial_data$Inputs$Tariff$'Export ($/kWh)' * 100,3), "c, " , "Discount Rate = ", financial_data$Inputs$Finance$'Discount Rate (%)' * 100, "%\n" , "Import Inflation = ", financial_data$Inputs$Tariff$'Import Inflation (%)' * 100, "%, " , "Export Inflation = ", financial_data$Inputs$Tariff$'Export Inflation (%)' * 100, "%\n" , "Maximum Profit = $", format(max(mat_npv), nsmall = 2), " and " , self_sufficiency, "% self-sufficient" , sep="")) + guides(fill=guide_colourbar(title="$")) } return(plot1) } # --------------------------------------------------------------------------------------------------------------- # Display the grid-dependency and annual energy exports for systems above the 5th NPV percentile (NPV Skim Top) # --------------------------------------------------------------------------------------------------------------- vis_Annual_Optimal <- function(simulation_info, technical_summary, financial_data, region_indexes, year) { num_pv <- length(technical_summary$'PV Capacity (Wp)') num_systems <- length(region_indexes) legend_type <- 0 #0 = None, #1 = PV Only, #2 = PV-Batteries Only, #3 = PV and PV-Batteries # Error condition: return an empty plot if (num_systems <= 0) { df <- data.frame() plot1 <- ggplot(df) + geom_point() + theme_bw() + xlim(0,10) + ylim(0,100) + xlab("Annual Energy Exported (MWh)") + ylab("Grid Dependency (%)") + ggtitle("Near-Optimal DER Configurations", subtitle = paste("Top ", simulation_info$'NPV Skim Top (%)'*100, "% of NPV\n", "Year ", year, "\n\n", sep="")) # Normal condition } else { maximum_index <- which(financial_data$'System.NPV'==max(financial_data$'System.NPV')) max_dependence <- NaN max_exported <- NaN df <- data.frame(exported = numeric(num_systems), dependence = numeric(num_systems), npv = numeric(num_systems), cfg_type = character(num_systems), stringsAsFactors = FALSE) for (i in 1:num_systems) { battery_index <- ceiling(region_indexes[i]/num_pv) pv_index <- (region_indexes[i]-1) %% num_pv + 1 # Figure out if all systems in the region are either PV-only, PV-Battery or a mix of both if (technical_summary$'Battery Capacity (Wh)'[battery_index] == 0) { df$cfg_type[i] = "PV" if (i == 1) { legend_type <- 1 # PV only } else { if (legend_type == 2) { legend_type <- 3 # PV and PV-Batteries } } } else { df$cfg_type[i] = "PV-Battery" if (i == 1) { legend_type <- 2 # PV-Batteries Only } else { if (legend_type == 1) { legend_type <- 3 # PV and PV-Batteries } } } # Record the data for the plot df$exported[i] <- technical_summary$Total$'Exported (kWh)'[pv_index, battery_index]/1000/technical_summary$Inputs$Timeline$Info$'Operational Years' df$dependence[i] <- technical_summary$Total$'Dependence (%)'[pv_index, battery_index]*100 df$npv[i] <- financial_data$'System.NPV'[pv_index, battery_index] if (region_indexes[i] == maximum_index[1]) { max_dependence <- technical_summary$Total$'Dependence (%)'[pv_index, battery_index]*100 max_exported <- df$exported[i] } } # Figure out the length shapes if (legend_type == 1) { # All systems in the region are PV-only manual_shapes = c(17) } if (legend_type == 2) { # All systems in the region are PV-Battery manual_shapes = c(15) } if (legend_type == 3) { # All systems in the region are a mix of PV-only and PV-Battery manual_shapes = c(17, 15) } df[order(df$cfg_type, df$npv),] plot1 <- ggplot(df) + aes(x=exported, y=dependence,z=npv) + geom_point(size=4, aes(colour=npv, shape=factor(cfg_type))) + scale_colour_gradient(low = "lightgrey", high = "black") + # scale_shape_identity() + scale_shape_manual(values=manual_shapes) + theme_bw() + coord_cartesian(xlim=c(0,10), ylim=c(0,100)) + xlab("Annual Energy Exported (MWh)") + ylab("Grid Dependency (%)") + annotate("point", size=2, shape=16, colour="green", x=max_exported, y=max_dependence) + guides(colour=guide_colourbar(title="NPV $", order=2), shape=guide_legend(title="", label=TRUE, order=1)) + ggtitle("Near-Optimal DER Configurations", subtitle = paste("Top ", simulation_info$'NPV Skim Top (%)'*100, "% of NPV\n", "Year ", year, "\n\n", sep="")) } return(plot1) } ################################################################################################################# # Scenario ################################################################################################################# # --------------------------------------------------------------------------------------------------------------- # # --------------------------------------------------------------------------------------------------------------- vis_Scenario_EnergyDependence <- function(economic_summary) {# plot(vis_Scenario_EnergyDependence(economic_summary)) # Combine the annual system information into a format for ggplot y <- data.table(year = economic_summary$'Economic Outcomes'$year, '% Grid Dependence' = economic_summary$'Economic Outcomes'$dependence, '% Dispatchable Self Consumption' = economic_summary$'Economic Outcomes'$gen_dispatch, '% Non Dispatchable Export' = economic_summary$'Economic Outcomes'$gen_nondispatch) y_melt <- melt(y, id.vars = c("year"), variable.name = "group", value.name = "y") ymin <- data.table(year = economic_summary$'Economic Outcomes'$year, '% Grid Dependence' = economic_summary$'Economic Outcomes'$dependence.min, '% Dispatchable Self Consumption' = economic_summary$'Economic Outcomes'$gen_dispatch.min, '% Non Dispatchable Export' = economic_summary$'Economic Outcomes'$gen_nondispatch.min) ymin_melt <- melt(ymin, id.vars = c("year"), variable.name = "group", value.name = "ymin") ymax <- data.table(year = economic_summary$'Economic Outcomes'$year, '% Grid Dependence' = economic_summary$'Economic Outcomes'$dependence.max, '% Dispatchable Self Consumption' = economic_summary$'Economic Outcomes'$gen_dispatch.max, '% Non Dispatchable Export' = economic_summary$'Economic Outcomes'$gen_nondispatch.max) ymax_melt <- melt(ymax, id.vars = c("year"), variable.name = "group", value.name = "ymax") combined <- merge(y_melt, ymin_melt, by=c('year', 'group')) combined <- merge(combined, ymax_melt, by=c('year', 'group')) # Determine breaks initial_year <- min(economic_summary$'Economic Outcomes'$year) final_year <- max(economic_summary$'Economic Outcomes'$year) y_lim_max <- ceiling(max( economic_summary$'Economic Outcomes'$dependence, economic_summary$'Economic Outcomes'$gen_dispatch.max, economic_summary$'Economic Outcomes'$gen_nondispatch.max, 100) / 20) * 20 y_breaks <- 0:(y_lim_max/20)*20 # Setup the colour palette colour_fill <- rep("", 3) names(colour_fill) <- levels(combined$group) colour_fill['% Grid Dependence'] <- 'darkolivegreen3' colour_fill['% Dispatchable Self Consumption'] <- 'cadetblue3' colour_fill['% Non Dispatchable Export'] <- 'lightpink2' colour_line <- rep("", 3) names(colour_line) <- levels(combined$group) colour_line['% Grid Dependence'] <- 'forestgreen' colour_line['% Dispatchable Self Consumption'] <- 'darkturquoise' colour_line['% Non Dispatchable Export'] <- 'red4' # Prepare the subtitle subtitle <- paste0("Household: ", economic_summary$Scenario$Load$Profile, " @ ", economic_summary$Scenario$Load$'Annual Consumption (MWh)', " MWh pa\n", "Import: ", economic_summary$Scenario$`Electricity Tariffs`$`Import ($/kWh)`*100, "c/kWh, ", print_Growth_Rate(economic_summary$Scenario$`Electricity Tariffs`$`Import Inflation (%)`), "; ", "Export: ", economic_summary$Scenario$`Electricity Tariffs`$`Export ($/kWh)`*100, "c/kWh, ", print_Growth_Rate(economic_summary$Scenario$`Electricity Tariffs`$`Export Inflation (%)`), "\n", "PV: $", economic_summary$Scenario$`System Costs`$`PV Cost ($/kWp)`, "/kWp, ", print_Growth_Rate(economic_summary$Scenario$`System Costs`$`PV Cost Change (%)`), "; ", "Battery: $", economic_summary$Scenario$`System Costs`$'Battery Cost ($/kWh)', "/kWh, ", print_Growth_Rate(economic_summary$Scenario$`System Costs`$`Battery Cost Change (%)`), "; ", "Discount Rate: ", economic_summary$Scenario$Financial$`Discount Rate (%)`*100, "%") # Generate the plot plot1 <- ggplot(combined, aes(x=year, y=y, group=group, colour=group)) + scale_colour_manual(name = paste0(">", print_percentile(1-economic_summary$Scenario$Simulation$`NPV Skim Top (%)`), " (NPV)"), values = colour_line) + scale_fill_manual(name = paste0(">", print_percentile(1-economic_summary$Scenario$Simulation$`NPV Skim Top (%)`), " (NPV)"), values = colour_fill) + geom_ribbon(aes(ymin=ymin,ymax=ymax,fill=group),colour=NA,alpha=0.2) + geom_line(size = 1) + ggtitle(paste0("Most Profitable Customer DER Systems, ", initial_year, "-", final_year, " [", economic_summary$Scenario$'Name', "]"), subtitle = subtitle) + theme_bw() + theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust=0.5)) + scale_y_continuous(name="% of Base Household Energy Load", breaks=y_breaks) + scale_x_continuous(name="", breaks=economic_summary$'Economic Outcomes'$year, minor_breaks=NULL, expand=c(0.01,0)) + coord_cartesian(ylim=c(0,y_lim_max)) + theme(legend.position = c(0.99, 0.99), legend.justification=c("right","top"), legend.background = element_rect(fill=alpha('white', 0.05))) return(plot1) } # --------------------------------------------------------------------------------------------------------------- # # --------------------------------------------------------------------------------------------------------------- vis_scenario_ResultsNPV <- function(df) { initial_year <- df$year[1] final_year <- df$year[length(df$year)] y_lim_max <- ceiling(max(df$npv_max.max) / 2000) * 2000 y_breaks <- 0:(y_lim_max/2000)*2000 plot1 <- ggplot( data = df, aes(x=year)) + geom_ribbon(aes(ymin=npv_max.min, ymax=npv_max.max), fill="darkolivegreen3", alpha=0.2) + geom_line(aes(y=npv_max), colour="forestgreen", size = 1) + ggtitle(paste0("Change in Customer NPV ", initial_year, "-", final_year)) + theme_bw() + theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust=0.5)) + scale_y_continuous(name="NPV ($)", breaks=y_breaks) + scale_x_continuous(name="", breaks=df$year, minor_breaks=NULL, expand=c(0.01,0)) + coord_cartesian(ylim=c(0,y_lim_max)) return(plot1) } # --------------------------------------------------------------------------------------------------------------- # # --------------------------------------------------------------------------------------------------------------- vis_Scenario_ResultsPayback <- function(df) { initial_year <- df$year[1] final_year <- df$year[length(df$year)] y_lim_max <- ceiling(max(df$payback.max) / 2) * 2 y_breaks <- 0:(y_lim_max/2)*2 plot1 <- ggplot( data = df, aes(x=year)) + geom_ribbon(aes(ymin=payback.min, ymax=payback.max), fill="darkolivegreen3", alpha=0.2) + geom_line(aes(y=payback), colour="forestgreen", size = 1) + ggtitle(paste0("Change in Customer Payback Periods ", initial_year, "-", final_year)) + theme_bw() + theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust=0.5)) + scale_y_continuous(name="Years", breaks=y_breaks) + scale_x_continuous(name="", breaks=df$year, minor_breaks=NULL, expand=c(0.01,0))+ coord_cartesian(ylim=c(0,y_lim_max)) return(plot1) } # --------------------------------------------------------------------------------------------------------------- # # --------------------------------------------------------------------------------------------------------------- vis_Scenario_FinancialAssumptions <- function(df) { initial_year <- df$year[1] final_year <- df$year[length(df$year)] df_flat <- melt(df, id="year") plot1 <- ggplot( data = df_flat, aes( x= year, y=value, colour=variable )) + geom_line() + ggtitle(paste("Economic Changes ", initial_year, "-", final_year, sep="")) + theme_bw() + theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust=0.5)) + scale_y_continuous(name="% change") + scale_x_continuous(name="", breaks=df_flat$year, minor_breaks=NULL, expand=c(0.01,0)) + coord_cartesian(ylim=c(-100,max(100, max(df$growth_tariff_import, df$growth_tariff_export)))) return(plot1) }