Skip to content

Instantly share code, notes, and snippets.

@alekrutkowski
Last active May 17, 2024 09:30
Show Gist options
  • Save alekrutkowski/e2ddbb99b6bc375ca51e9fa886830204 to your computer and use it in GitHub Desktop.
Save alekrutkowski/e2ddbb99b6bc375ca51e9fa886830204 to your computer and use it in GitHub Desktop.
JAF2R_SpecGen – JAF specification generator for indicators based on Eurostat data for JAF2R (https://github.com/alekrutkowski/JAF2R)
options(shiny.sanitize.errors = FALSE)
library(shiny)
library(data.table)
library(eurodata)
library(magrittr)
MetaBase <-
importMetabase() %>%
as.data.table()
DataList <-
importDataList() %>%
as.data.table()
DATASETS <-
data.table(Code=unique(MetaBase$Code)) %>%
merge(unique(DataList[,.(Code,`Dataset name`)]),
by='Code') %>%
{set_names(.$Code,
paste0('[',.$Code,'] ',.$`Dataset name`))}
# App definition ----------------------------------------------------------
ui <- fluidPage(
titlePanel(HTML("<center>JAF specification generator for indicators based on Eurostat data</center>"),
'JAF spec gen'),
tags$head(HTML(r'{
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://fonts.googleapis.com/css2?family=Reddit+Mono&display=swap" rel="stylesheet">
<style>
* {
font-family: 'Reddit Mono', monospace;
}
pre {
font-family: 'Reddit Mono', monospace;
font-size: 16px;
}
/* Hide minor ticks */
.irs-grid-pol { display: none; }
#TheCode {
position: fixed;
top: 120px; # Distance from the top of the viewport
right: 10px; # Distance from the right of the viewport
z-index: 99; # Ensures it floats above other content
background-color: white; # Optional: changes background color
border: 1px solid #ddd; # Optional: adds a border
padding: 5px; # Optional: adds space inside the box
box-shadow: 0px 0px 5px rgba(0,0,0,0.5); # Optional: adds shadow for better visibility
}
.button-copy {
z-index: 100;
cursor: pointer;
padding: 5px 10px;
margin: 5px;
background-color: #428bca;
color: white;
border: none;
border-radius: 5px;
position: fixed; // Position the button absolutely
top: 10px; // 5px from the top of the container
right: 10px; // 5px from the right of the container
}
</style>
<script>
document.addEventListener("DOMContentLoaded", function() {
var pres = document.querySelectorAll('pre');
pres.forEach(function(pre) {
// Check if the pre element contains the string "Scroll up and correct"
if (!pre.textContent.includes("Scroll up and correct")) {
// Wrap pre in a container if not already wrapped
if (!pre.parentNode.classList.contains('pre-container')) {
var wrapper = document.createElement('div');
wrapper.className = 'pre-container';
pre.parentNode.insertBefore(wrapper, pre);
wrapper.appendChild(pre);
} else {
var wrapper = pre.parentNode;
}
// Create and add the button
var btn = document.createElement('button');
btn.innerText = 'Copy to Clipboard';
btn.className = 'button-copy';
wrapper.appendChild(btn);
btn.onclick = function() {
var textArea = document.createElement('textarea');
textArea.value = pre.textContent;
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
alert('Copied to clipboard!');
};
}
});
});
</script>
}')),
column(6,
textInput('JAF_KEY',
'Type the structurally correct JAF_KEY code of the new indicator e.g. PA1c.C1.55-74',width = "100%"),
textInput('IndicName',
'Type the descriptive full name of the indicator',width = "100%"),
textInput('IndicUnit',
'Type the name of the unit of indicator levels, e.g. %',width = "100%"),
textInput('IndicChangeUnit',
'Type the name of the unit of indicator changes, e.g. p.p.',width = "100%"),
sliderInput(
inputId = "NumOfDatasets",
label = "Number of datatsets: 1 for a simple definition, 2 or more for a multi-dataset formula-based definition",
min = 1L, max = 10L, value = 1L, ticks=TRUE, round=TRUE, step=1L, width = "100%"
),
conditionalPanel(
condition = "input.NumOfDatasets>1",
textInput('Formula',
HTML('Type the math formula using the appropriate letters (a, b, c, d, etc.) as variables,<br>e.g. (a + b)/(c*d)'),width = "100%")),
HTML('\u24D8 In the selection fields below, you can type to search, use <kbd>Delete</kbd> or <kbd>\u232B Backspace</kbd> keyboard keys to delete, and use <kbd>Esc</kbd> to hide the drop-down menu.'),
br(),br(),
uiOutput('DatasetSelections'),
br(),
uiOutput('DimSelections'),
sliderInput(
inputId = "CompendiumNum",
label = "Compendium Excel file number",
min = 1L, max = 10L, value = 1L, ticks=TRUE, round=TRUE, step=1L, width = "100%"
),
checkboxInput(
inputId = "HighIsGood",
label = strong("Tick if high values of the indicator are economically or socially good (untick if bad)"),
value = TRUE, width = "100%"
),
checkboxInput(
inputId = "IsCountryIndic",
label = strong("Tick if the indicator should be included in the Country Compendia"),
value = TRUE, width = "100%"
)),
column(6,br(),verbatimTextOutput('TheCode'))
)
verify_JAF_KEY <- function(txt) {
if (!grepl("^PA\\d+(\\.\\d)?[a-z]?(\\d+)?\\.[OSC]\\d+\\..*",txt))
stop('Empty or structurally wrong JAF_KEY! Scroll up and correct.') else txt
}
sanitizeTxt <- function(txt)
gsub('"',r'{\"}',txt,fixed=TRUE)
verifyFormula <- function(txt) {
if (txt=="") stop('Empty formula! Scroll up and type the formula.')
tryCatch(parse(text=txt),
error=function(e) stop('Wrong formula!\n',e))
txt
}
server <- function(input, output) {
Dims <- function(input,letter)
MetaBase[Code==input[[paste0("DatasetCode",letter)]],
Dim_name] %>% unique %>%
setdiff(c('geo','time'))
DimsDimValsPair <- function(input,letter,DimName)
paste0(DimName,'="',input[[paste0(letter,DimName)]],'"')
DimsDimValsPairs <- function(input,letter)
Dims(input,letter) %>%
sapply(\(DimName) DimsDimValsPair(input,letter,DimName)) %>%
paste(collapse=', ')
output$DatasetSelections <- renderUI(
lapply(letters[seq_len(input$NumOfDatasets)],
function(letter)
selectInput(
inputId = paste0("DatasetCode",letter),
label = paste0("Select Dataset '",letter,"'"),
selected='nama_10_gdp',
choices = DATASETS,
multiple=FALSE,
width='100%')))
output$DimSelections <- renderUI(
lapply(letters[seq_len(input$NumOfDatasets)],
function(letter) list(
lapply(Dims(input,letter),
function(DimName)
selectInput(
inputId = paste0(letter,DimName),
label = paste0('\u25B6 ',letter,"\u2019s ",DimName,' ='),
choices = MetaBase[Code==input[[paste0("DatasetCode",letter)]] &
Dim_name==DimName,
.(Dim_val)] %>%
setnames('Dim_val',DimName) %>%
merge(importLabels(DimName), by=DimName) %>%
{set_names(.[[DimName]],
paste0('[',.[[DimName]],'] ',.[[paste0(DimName,'_labels')]]))},
multiple=FALSE,
width='100%')
),br())))
output$TheCode <- renderText(
if (input$NumOfDatasets==1)
paste0('
inside(JAF_INDICATORS, indicator_named = "',verify_JAF_KEY(input$JAF_KEY),
'") =
specification(
name = "',sanitizeTxt(input$IndicName),'",
unit_of_level = "',sanitizeTxt(input$IndicUnit),'",
unit_of_change = "',sanitizeTxt(input$IndicChangeUnit),'",
indicator_groups = "COMPENDIUM ',input$CompendiumNum,
ifelse(as.logical(input$IsCountryIndic),' COUNTRY',""),'",
source = "Eurostat",
high_is_good = ',input$HighIsGood,',
value = fromEurostatDataset("',input$DatasetCodea,'",
with_filters(',DimsDimValsPairs(input,'a'),'))
)
') else
paste0('
inside(JAF_INDICATORS, indicator_named = "',verify_JAF_KEY(input$JAF_KEY),
'") =
specification(
name = "',sanitizeTxt(input$IndicName),'",
unit_of_level = "',sanitizeTxt(input$IndicUnit),'",
unit_of_change = "',sanitizeTxt(input$IndicChangeUnit),'",
indicator_groups = "COMPENDIUM ',input$CompendiumNum,
ifelse(as.logical(input$IsCountryIndic),' COUNTRY',""),'",
source = "Eurostat",
high_is_good = ',input$HighIsGood,',
value = fromFormula(',verifyFormula(input$Formula),',
where = variables(
',letters[seq_len(input$NumOfDatasets)] %>%
sapply(function(letter)
paste0(' ',letter,' = fromEurostatDataset("',input[[paste0('DatasetCode',letter)]],'",
with_filters(',DimsDimValsPairs(input,letter),'))')) %>%
paste(collapse=',\n'),'
))
)
')
)
}
# Run the application
shinyApp(ui = ui, server = server)
@alekrutkowski
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment