commit 54f5e8c41837dfb86528875ac8917e9e30e5a39c Author: spegg Date: Fri Feb 1 10:28:28 2019 +0000 Initial Script diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..7c319b5 --- /dev/null +++ b/.hgignore @@ -0,0 +1,4 @@ +syntax: glob +.RData +.Rhistory +archive diff --git a/MESOonline.R b/MESOonline.R new file mode 100644 index 0000000..5ed54d6 --- /dev/null +++ b/MESOonline.R @@ -0,0 +1,218 @@ +library(DT) +library(shiny) +library(shinyBS) +library(shinyjs) +library(shinydashboard) +library(htmltools) +library(DiagrammeR) +library(magrittr) +library(plotly) +library(shiny) +library(googleway) +library(Rgraphviz) + +addResourcePath("js", "./www/js") + +source('Parses.R') + +# shinyInput <- function(FUN, len, id, ...) { +# inputs <- character(len) +# for (i in seq_len(len)) { +# inputs[i] <- as.character(FUN(paste0(id, i), ...)) +# } +# inputs +# } + +ui<-dashboardPage( + dashboardHeader(title = "JNCC MESO online"), + dashboardSidebar( + sidebarMenu(id = "tabs", + menuItem("Pressure Test", tabName = "1", icon = icon("align-left", lib = "glyphicon")), + menuItem("Bayesian Network", tabName = "2", icon = icon("plug")), + menuItem("Habitats", tabName = "3", icon = icon("map")), + selectInput(ns("modelSelect"), "Select MESO model", choices=ns("output$selModels"), selected=NULL, multiple=FALSE) + ) + ), + dashboardBody( + tabItems( + tabItem(tabName = "1",h4("Pressure Test"), + fluidPage( + fluidRow( + column(width=3, + radioButtons(ns("pressure1"), 'Sediment type', choices=c('On', 'Off'), inline=TRUE), + radioButtons(ns("pressure2"), 'Seabed type', choices=c('On', 'Off'), inline=TRUE), + radioButtons(ns("pressure3"), 'Material extraction', choices=c('On', 'Off'), inline=TRUE), + radioButtons(ns("pressure4"), 'Abrasion of seabed', choices=c('On', 'Off'), inline=TRUE), + radioButtons(ns("pressure5"), 'Penetration of seabed', choices=c('On', 'Off'), inline=TRUE), + radioButtons(ns("pressure6"), 'Siltation', choices=c('On', 'Off'), inline=TRUE), + radioButtons(ns("pressure7"), 'Wave exposure', choices=c('On', 'Off'), inline=TRUE), + radioButtons(ns("pressure8"), 'Suspended sediment', choices=c('On', 'Off'), inline=TRUE), + radioButtons(ns("pressure9"), 'Generic contamination', choices=c('On', 'Off'), inline=TRUE), + radioButtons(ns("pressure10"), 'Deoxygenation', choices=c('On', 'Off'), inline=TRUE), + radioButtons(ns("pressure11"), 'Removal of target species', choices=c('On', 'Off'), inline=TRUE), + actionButton(ns("calcAB"), "Calc") + ), + column(width=9, + plotlyOutput(ns("layer1"), height="300px") %>% withSpinner(), + plotlyOutput(ns("layer2"), height="300px") %>% withSpinner(), + plotlyOutput(ns("layer3"), height="300px") %>% withSpinner() + ) + ) + ) + ), + tabItem(tabName = "2",h4("Bayesian Network"), + fluidPage( + fluidRow( + plotOutput(ns("bbnGraphPlot")) + ), + fluidRow( + column( + width=6, + DT::dataTableOutput(ns('nodeTable')) + ), + column( + width=6, + DT::dataTableOutput(ns('edgeTable')) + ) + ) + ) + ), + tabItem(tabName = "3",h4("Habitats"), + fluidPage( + google_mapOutput(outputId = "map", width = "100%", height = "400px") + ) + ) + ) + ) +} + +server <- function(input, output, session) { + #SERVER Constants + + print('Loading data') + + set_key("AIzaSyAw8_btgGN1drf8qhCxNcotP6r11qEXA_M") + dataStorage <- 'data/' + + models<-NULL + selectedModel <- NULL + + availableModels <- function() return(models) + + observeEvent(input$modelSelect, { + selectedModel <<- match(input$modelSelect, models) + }) + + output$map <- renderGoogle_map({ + google_map(location = c(0, 55), zoom = 10) + }) + + #parse on load sheets in the input sheet folder + + modelList <- list() + fileList <- list.files(dataStorage, pattern='.xlsx') + + print(fileList) + + for (idx in 1:length(fileList)) { + tmp <- parseSheets(fileList[idx], plot=FALSE) + + if (!is.null(tmp)) { + modelList[[cnt]] <- tmp + models <<- c(models, substring(fileList[idx], start=1, stop=nchar(fileList[idx])-5)) + print(paste('Model file successfully loaded', fileList[idx])) + cnt=cnt+1 + } + } + + output$nodeTable <- DT::renderDataTable(modelList[[selectedModel]]$pressEcoServMap$nodes, + selection = 'single',options = list(searching = FALSE,pageLength = 10),server = TRUE, escape = FALSE,rownames= TRUE) + + output$edgeTable <- DT::renderDataTable(modelList[[selectedModel]]$pressEcoServMap$edges, + selection = 'single',options = list(searching = FALSE,pageLength = 10),server = TRUE, escape = FALSE,rownames= TRUE) + + + output$bbnGraphPlot <- renderPlot({ + graphviz.plot(modelList[[selectedModel]]$p_es$net) + }) + + output$layer1 <- renderPlotly({ + calcLikelihood(plot=TRUE) + }) + + output$layer2 <- renderPlotly({ + calcLikelihood(plot=TRUE) + }) + + output$layer3 <- renderPlotly({ + calcLikelihood(plot=TRUE) + }) + + # TaskRow <- eventReactive(input$select_button,{ + # taskList[SelectedRow(),2:ncol(taskList)] + # }) + # + # points <- eventReactive(input$recalc, { + # cbind(rnorm(40) * 2 + 13, rnorm(40) + 48) + # }, ignoreNULL = FALSE) + # + # cities <- read.csv(textConnection(" + # City,Lat,Long,Pop + # Boston,42.3601,41.0589,645966 + # Hartford,41.7627,42.6743,125017 + # New York City,40.7127,44.0059,8406000 + # Philadelphia,39.9500,45.1667,1553000 + # Pittsburgh,40.4397,39.9764,305841 + # Providence,41.8236,41.4222,177994 + # ")) + + # output$mymap <- renderLeaflet({ + # leaflet(cities) %>% addTiles() %>% + # addCircles(lng = ~Long, lat = ~Lat, weight = 1, + # radius = ~sqrt(Pop) * 30, popup = ~City + # ) + # }) + + # output$mymap1 <- renderUI({ + # require(maptools) + # + # data(meuse) + # coordinates(meuse) = ~x+y + # proj4string(meuse) <- CRS("+init=epsg:28992") + # + # data(meuse.grid) + # coordinates(meuse.grid)<-c('x','y') + # meuse.grid<-as(meuse.grid,'SpatialPixelsDataFrame') + # im<-as.image.SpatialGridDataFrame(meuse.grid['dist']) + # cl<-ContourLines2SLDF(contourLines(im)) + # str(cl) + # proj4string(cl) <- CRS('+init=epsg:28992') + # + # #m <- plotGoogleMaps(meuse, filename = 'myMap1.html', openMap = FALSE, streetViewControl = TRUE) + # + # # Combine point and line data + # m<-plotGoogleMaps(meuse, mapTypeId='TERRAIN', openMap = FALSE) + # #mapMeusePoints<- plotGoogleMaps(cl, mapTypeId='TERRAIN', openMap = FALSE) + # comboMap<- plotGoogleMaps(cl,add=TRUE, previousMap=m, openMap = FALSE, layerName='Lines') + # finalMap<- plotGoogleMaps(comboMap, filename = '/home/spegg/R projects/draw/www/myMap1.htm', openMap = FALSE) + # + # tags$iframe( + # srcdoc = paste(readLines('/home/spegg/R projects/draw/www/myMap1.htm'), collapse = '\n'), + # width = "100%", + # height = "600px" + # ) + # }) + # + # output$popup <- renderUI({ + # bsModal("taskModal", paste0("Task information for: ",SelectedRow()), "", size = "large", + # column(12, + # DT::renderDataTable(TaskRow()) + # ), + # column(6, + # actionButton("openTask", "Open Task") + # ) + # ) + # }) +} + +shinyApp(ui('JNCC'), function(...) {callModule(server, 'JNCC')}) \ No newline at end of file diff --git a/Parses.R b/Parses.R new file mode 100644 index 0000000..312582d --- /dev/null +++ b/Parses.R @@ -0,0 +1,328 @@ + +modules::import(openxlsx) +modules::import(bnlearn) +modules::import(stringr) +modules::import(graph) +modules::import(ggplot2) +modules::import(stats) +modules::import(plotly) +modules::import(utils) + + +#Improvements needed: make the selection of first row/column of nodes programmatic +FIRST_NODE_COL <- 3 + +mappings <- c('TestScenario', 'Map_P_BA', 'Map_BA_OP', 'Map_OP_ES') +nodeTypes <- c('Input.Nodes', 'Internal.Nodes', 'Published.Nodes') +states <- c('impact', 'confidence', 'growth', 'recovery') +refs <-c(1:length(mappings)) + +setEmpties <- function(val) { + if (is.na(val)) return(0) else return(val) +} + +readXL <- function(fName, sheetN, startRow=1) { + xl <- read.xlsx(fName, sheet = sheetN, startRow) #, rowNames = import) + return(data.frame(xl, stringsAsFactors = FALSE, row.names = NULL)) +} + +delNA <- function(vec) { + return(vec[!is.na(vec)]) +} + +parseScenario <- function(press, prefix = 'p') { + pressNames <- colnames(press)[2:length(colnames(press))] + coefs <- matrix(data=NA, nrow=length(pressNames), ncol=2, dimnames=list(NULL, c('growth', 'confidence'))) + for (col in 2:ncol(press)) { + coefs[col-1,] <- as.numeric(split(press[1, col]))[match(c('growth', 'confidence'), states)] + } + press[is.na(press)] <- 0 + if (sum(duplicated(pressNames))>0) { + cat('Duplicated pressure node names found') + print(pressNodes[duplicated(pressNames)]) + } + + return(list( + timeSeq=press, + nodes=data.frame(name = pressNames, + code=paste0(prefix, seq(1:length(pressNames))), + growth = coefs[,'growth'], + confidence=coefs[,'confidence'], + stringsAsFactors = FALSE), + edges=data.frame(input=NULL, output=NULL, impact=NULL) + )) +} + +getInitial <- function(string, letter) { + return(tolower(substr(string, start=1, stop=1))) +} + +split <- function(cell) { + params <- unlist(strsplit(cell, ',')) + values <- rep(0, length(states)) + + for (n in 1:length(params)) { + kvp <- unlist(strsplit(params[n], '=')) + ref <- match(getInitial(trimws(kvp[1])), getInitial(states)) + if ((ref>0) & (ref<=length(values))) { + values[ref] <- kvp[2] + } else { + print(paste('Unrecognised parameter(s):',params[n])) + } + + } + return(values) + +} + +cleanTitles <- function(titleV) { + return(str_replace_all(titleV, c(' ' = '.', '-' = ''))) +} + +getOutNodes <- function(codes, codeList) { + v <- vector(mode='logical', length=length(codes)) + for (idx in 1:length(codes)) { + v[idx] <- (sum(startsWith(codes[idx], codeList))>0) + } + return(v) +} + +buildGraph <- function(model, desc) { + + #model contains the following + # node table, edge table + + #descriptor (desc) contains: + #inputCode - the top layer of the model + #outputCodes - all subsequent layers to be included in the model + + inputNodes <- model$nodes$code[which(startsWith(model$nodes$code, desc$inputCode))] + inputText <- paste0("[", inputNodes, "]", collapse ="") + + #do the internal nodes + edges <- "" + + outNodes <- model$nodes$code[getOutNodes(model$nodes$code, desc$outputCodes)] + outDist <- vector(mode="list", length=length(outNodes)) + + for (idx in 1:length(outNodes)) { + nodeRef <- match(outNodes[idx], model$nodes$code) + + rows <- which(model$edges$output == outNodes[idx]) + inputsStr <- paste0(model$edges$input[which(model$edges$output == outNodes[idx])], sep=":", collapse="") + edges <- paste0(edges, paste0("[", outNodes[idx], "|", substr(inputsStr, start=1, stop=(nchar(inputsStr)-1)), "]")) + + #Make the coefficient of the distribution + coefVal <- setNames(c(model$nodes$growth[nodeRef], model$edges$impact[rows]), + c("(Intercept)", model$edges$input[rows]) + ) + #str(coefVal) + outDist[[idx]] <- list(coef = coefVal, + sd = model$nodes$confidence[nodeRef]) + } + + print('about to build network') + + net <- model2network(paste0(inputText, edges)) + + print('network build successful') + + inDist <- vector(mode="list", length=length(inputNodes)) + + for (idx in 1:length(inputNodes)) { + inRef <- match(inputNodes[idx], model$nodes$code) + coefVal <- setNames(model$nodes$growth[inRef], "(Intercept)") + inDist[[idx]] <- list(coef = coefVal, sd = model$nodes$confidence[inRef]) + } + + allDists = as.list(setNames(c(inDist, outDist), c(inputNodes, outNodes))) + cfit = custom.fit(net, allDists) + + cat('about to calculate sample distributions') + print(outNodes) + + sampleDists <- cpdist(cfit, nodes = outNodes, evidence = TRUE, n = 10000, method = "lw") + summDists <- summary(sampleDists) + #stdDev <- sd(sampleDists) + + print('sample distribution build successful') + + model$edges$input <- model$nodes$name[match(model$edges$input, model$nodes$code)] + model$edges$output <- model$nodes$name[match(model$edges$output, model$nodes$code)] + + return( + list( + nodes = model$nodes, + edges = model$edges, + net = net, + cfit = cfit, + allDists = allDists, + summDists = summDists + ) + ) +} + + +getValidNodes <- function(mapping, prevOutputs, prefix) { + + #Find row id for input nodes, internal and published + inputNodes <- mapping[2:nrow(mapping),1] + + #check that all input nodes are in the previous table + inputNodes <- delNA(mapping[mapping[,"Node.Type"] == 'input', "Nodes"]) + if (length(inputNodes)>0) { + if (sum(inputNodes %in% prevOutputs$name)0) { + cat('Duplicated input node names found') + print(inputNodes[duplicated(inputNodes)]) + } + + outNodes <- delNA(colnames(mapping)[FIRST_NODE_COL:ncol(mapping)]) + if (sum(duplicated(outNodes))>0) { + cat('Duplicated output node names found') + print(outNodes[duplicated(outNodes)]) + } + + + #check that all internal nodes are in the columns + intNodes <- delNA(mapping[mapping[,"Node.Type"] == 'internal', "Nodes"]) + if (length(intNodes)>0) { + if (sum(intNodes %in% outNodes)0) { + + sheets <- sort(delNA(match(names, mappings))) + + cat('starting sheet parse') + print(sheets) + + if (sum(sheets==refs)==length(refs)) { + #read all mapping tables + scenario <- parseScenario(readXL(fName,mappings[1], startRow=1), prefix='p') + p_ba <- parseMapping(readXL(fName,mappings[2], startRow=1), scenario, prefix='ba') + p_op <- parseMapping(readXL(fName,mappings[3], startRow=1), p_ba, prefix='op') + p_es <- parseMapping(readXL(fName,mappings[4], startRow=1), p_op, prefix='es') + + print('building graphs') + + p_baNet <- buildGraph(p_ba, desc=list(inputCode='p', outputCodes='ba')) + p_opNet <- buildGraph(p_op, desc=list(inputCode='p', outputCodes=c('ba', 'op'))) + p_esNet <- buildGraph(p_es, desc=list(inputCode='p', outputCodes=c('ba', 'op', 'es'))) + + print('sheet load completed') + return( + list( + pressBioAss = p_baNet, + pressOpProc = p_opNet, + pressEcoServ = p_esNet + ) + ) + + } else { + print(paste('Sheets found include', mappings[sheets])) + cat('Missing sheets are:') + print(refs[-sheets]) + } + } + } +} diff --git a/app.R b/app.R new file mode 100644 index 0000000..c36f58c --- /dev/null +++ b/app.R @@ -0,0 +1,267 @@ +modules::import(DT) +modules::import(shiny) +modules::import(shinyBS) +modules::import(shinyjs) +modules::import(shinydashboard) +modules::import(htmltools) +modules::import(DiagrammeR) +modules::import(magrittr) +modules::import(plotly) +modules::import(kableExtra) +modules::import(Rgraphviz) +modules::import(knitr) +modules::import(shinycssloaders) +modules::import(googleway) +modules::import(Rgraphviz) +modules::import(bnlearn) + +parser <- modules::use('Parses.R') + +layers <- c("Pressures to Bio-Assemblages", "Bio-Assemblages to Output Processes", "Output Processes to Eco-system services") +transitions <- c("Pressures to Bio-Assemblages", "Pressures to Output Processes", "Pressures to Eco-system services") +addResourcePath("js", "./www/js") + +ui<-dashboardPage( + dashboardHeader(title = "JNCC MESO online"), + dashboardSidebar( + sidebarMenu(id = "tabs", + menuItem("Pressure Test", tabName = "1", icon = icon("arrow-down")), + menuItem("Bayesian Network", tabName = "2", icon = icon("atom")), + menuItem("Habitats", tabName = "3", icon = icon("atlas")), + selectInput("modelSelect", "Select MESO model", choices=c(""), selected=NULL, multiple=FALSE), + selectInput("layerSelect", "Select Transition", + choices=transitions, + selected=NULL, multiple=FALSE) + ) + ), + dashboardBody( + tabItems( + tabItem(tabName = "1", + fluidRow( + column(width=2, + h4('Pressure Test'), + radioButtons("pressure1", 'Sediment type', choices=c('On', 'Off'), inline=TRUE), + radioButtons("pressure2", 'Seabed type', choices=c('On', 'Off'), inline=TRUE), + radioButtons("pressure3", 'Material extraction', choices=c('On', 'Off'), inline=TRUE), + radioButtons("pressure4", 'Abrasion of seabed', choices=c('On', 'Off'), inline=TRUE), + radioButtons("pressure5", 'Penetration of seabed', choices=c('On', 'Off'), inline=TRUE), + radioButtons("pressure6", 'Siltation', choices=c('On', 'Off'), inline=TRUE), + radioButtons("pressure7", 'Wave exposure', choices=c('On', 'Off'), inline=TRUE), + radioButtons("pressure8", 'Suspended sediment', choices=c('On', 'Off'), inline=TRUE), + radioButtons("pressure9", 'Generic contamination', choices=c('On', 'Off'), inline=TRUE), + radioButtons("pressure10", 'Deoxygenation', choices=c('On', 'Off'), inline=TRUE), + radioButtons("pressure11", 'Removal of target species', choices=c('On', 'Off'), inline=TRUE), + actionButton("calcAB", "Calc") + ), + column(width=10, + h4('Effect on bio-assemblage'), + plotlyOutput("layer1", height="270px") %>% withSpinner(), + h4('Effect on Output Processes'), + plotlyOutput("layer2", height="270px") %>% withSpinner(), + h4('Effect on Eco-system services'), + plotlyOutput("layer3", height="270px") %>% withSpinner() + ) + ) + ), + tabItem(tabName = "2",h4("Bayesian Network"), + fluidPage( + fluidRow( + plotOutput("bbnGraphPlot") + ), + fluidRow( + column( + width=6, + h4('Ecoservice nodes'), + DT::dataTableOutput('nodeTable') + ), + column( + width=6, + h4('Ecoservice influences'), + DT::dataTableOutput('edgeTable') + ) + ) + ) + ), + tabItem(tabName = "3",h4("Habitats"), + fluidPage( + google_mapOutput(outputId = "map", width = "100%", height = "750px") + ) + ) + ) + ) +) + +server <- function(input, output, session) { + #SERVER Constants + + print('Loading data') + + set_key("AIzaSyAw8_btgGN1drf8qhCxNcotP6r11qEXA_M") + dataStorage <- 'data/' + + models<-NULL + + getAvailableModels <- function() { + fileList <- list.files(dataStorage, pattern='.xlsx') + + print(fileList) + modelList <- list() + cnt<-1 + + for (idx in 1:length(fileList)) { + print(paste('attempting to load', paste0(dataStorage, fileList[idx]))) + tmp <- parser$parseSheet(paste0(dataStorage, fileList[idx])) + + if (!is.null(tmp)) { + modelList[[cnt]] <- tmp + + #tidy up the list for displaying + + models <<- c(models, substr(fileList[idx], 1, (nchar(fileList[idx])-5))) + print(paste('Model file successfully loaded', fileList[idx])) + cnt=cnt+1 + + + } + } + + updateSelectInput(session, "modelSelect", choices=models) + + return(modelList) + } + + + .selections <- reactiveValues(model=1, layer=1) + + #parse on load sheets in the input sheet folder + modelList <- getAvailableModels() + + calcLikelihood <- function(layer) { + + isolate({ + if (layer==1) layerStr='ba' else if (layer==2) layerStr='op' else layerStr ='es' + nodeList <- modelList[[.selections$model]][[.selections$layer]]$nodes + str(nodeList) + nodeNames <- nodeList$name[startsWith(nodeList$code, layerStr)] + mean = runif(length(nodeNames), min=-1, max=1) + sd = runif(length(nodeNames), min=-0.25, max=0.25) + + df <- data.frame( + nodeNames = nodeNames, + range = c((mean - (3*sd)), (mean - (2*sd)), (mean - sd), mean, + (mean + sd), (mean + (2*sd)), (mean + (3*sd))), + stringsAsFactors=FALSE + ) + print(df) + }) + return( + df + ) + # isolate({ + # + # if (layer==1) layerStr='ba' else if (layer==2) layerStr='op' else if (layer==3) layerStr='es' + # + # layerRange <- which(startsWith(modelList[[.selections$model]][[layer]]$nodes, layerStr)) + # distList <- modelList[[.selections$model]][[layer]]$summDist[,layerRange] + # nodeNames <- modelList[[.selections$model]][[layer]]$nodes$name[layerRange] + # + # } + + # print(paste('Length of layer & node names',layer, length(nodeNames))) + # + # distList <- modelList[[.selections$model]][[layer]]$summDist + # colNames <- c('min', 'q1', 'q1', 'mean', 'q3', 'q3', 'max') + # distM <- matrix(data=NA, nrow=ncol(distList), ncol=length(colNames)) + # + # print(paste('Length of distributions',nrow(distM))) + + # for (col in 1:ncol(distList)) { + # valsAsStrs <- unlist(strsplit(distList[,col], ":")) + # valIdxs <- seq(from=2, to=length(valsAsStrs), by=2) + # distVals <- as.numeric(valsAsStrs[valIdxs]) + # distM[col,] <- c(distVals[1], distVals[2], distVals[2], distVals[4], distVals[5], distVals[5], distVals[6]) + # } + # }) + # + # df <- data.frame( + # nodeNames = nodeNames, + # dist = distM, + # stringsAsFactors=FALSE + # ) + # print(df) + # + # return( + # df + # ) + } + + + .likelihoods <-reactiveValues( + p_ba = calcLikelihood(1), + ba_os = calcLikelihood(2), + os_es = calcLikelihood(3) + ) + + observeEvent(input$modelSelect, { + .selections$model <<- match(input$modelSelect, models) + #print(.selections$model) + }) + + observeEvent(input$layerSelect, { + .selections$layer <<- match(input$layerSelect, transitions) + #print(.selections$layer) + }) + + observeEvent(input$calcAB, { + #print(paste('Action button pressed', input$calcAB)) + + .likelihoods$p_ba <<- calcLikelihood(1) + .likelihoods$ba_os <<- calcLikelihood(2) + .likelihoods$os_es <<- calcLikelihood(3) + + }) + + output$map <- renderGoogle_map({ + google_map(location = c(55, 0), zoom = 7) + }) + + + + + output$nodeTable <- DT::renderDataTable( + + modelList[[.selections$model]][[.selections$layer]]$nodes, + selection = 'single',options = list(searching = TRUE, pageLength = 10),server = TRUE, escape = FALSE,rownames= TRUE + ) + + output$edgeTable <- DT::renderDataTable( + + modelList[[.selections$model]][[.selections$layer]]$edges, + selection = 'single',options = list(searching = TRUE, pageLength = 10),server = TRUE, escape = FALSE,rownames= TRUE + ) + + output$bbnGraphPlot <- renderPlot({ + graphviz.plot(modelList[[.selections$model]][[.selections$layer]]$net) + }) + + output$layer1 <- renderPlotly({ + plot_ly(.likelihoods$p_ba, y = ~range, color = ~nodeNames, type = "box") + }) + + output$layer2 <- renderPlotly({ + #print(.likelihoods) + + if (.selections$layer>1) { + plot_ly(.likelihoods$ba_os, y = ~range, color = ~nodeNames, type = "box") + } + }) + + output$layer3 <- renderPlotly({ + + if (.selections$layer>2) { + plot_ly(.likelihoods$os_es, y = ~range, color = ~nodeNames, type = "box") + } + }) +} + +shinyApp(ui, server) \ No newline at end of file diff --git a/data/BaselineTestHabitat.xlsx b/data/BaselineTestHabitat.xlsx new file mode 100644 index 0000000..702d6e9 Binary files /dev/null and b/data/BaselineTestHabitat.xlsx differ diff --git a/data/WorkingTestHabitat.xlsx b/data/WorkingTestHabitat.xlsx new file mode 100644 index 0000000..702d6e9 Binary files /dev/null and b/data/WorkingTestHabitat.xlsx differ diff --git a/www/css/bootstrap.css b/www/css/bootstrap.css new file mode 100644 index 0000000..1ce0c27 --- /dev/null +++ b/www/css/bootstrap.css @@ -0,0 +1,9109 @@ +bootswatch.com + + +169-215 minutes +------------------------------------------------------------------------ + +/*! + * Bootswatch v4.0.0-beta.3 + * Homepage: https://bootswatch.com + * Copyright 2012-2018 Thomas Park + * Licensed under MIT + * Based on Bootstrap +*/ +/*! + * Bootstrap v4.0.0-beta.3 (https://getbootstrap.com) + * Copyright 2011-2017 The Bootstrap Authors + * Copyright 2011-2017 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +:root { + --blue: #033C73; + --indigo: #6610f2; + --purple: #6f42c1; + --pink: #e83e8c; + --red: #C71C22; + --orange: #fd7e14; + --yellow: #DD5600; + --green: #73A839; + --teal: #20c997; + --cyan: #2FA4E7; + --white: #fff; + --gray: #868e96; + --gray-dark: #343a40; + --primary: #2FA4E7; + --secondary: #e9ecef; + --success: #73A839; + --info: #033C73; + --warning: #DD5600; + --danger: #C71C22; + --light: #f8f9fa; + --dark: #343a40; + --breakpoint-xs: 0; + --breakpoint-sm: 576px; + --breakpoint-md: 768px; + --breakpoint-lg: 992px; + --breakpoint-xl: 1200px; + --font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + --font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} + +*, +*::before, +*::after { + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +html { + font-family: sans-serif; + line-height: 1.15; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + -ms-overflow-style: scrollbar; + -webkit-tap-highlight-color: transparent; +} + +@-ms-viewport { + width: device-width; +} + +article, aside, dialog, figcaption, figure, footer, header, hgroup, main, nav, section { + display: block; +} + +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #868e96; + text-align: left; + background-color: #fff; +} + +[tabindex="-1"]:focus { + outline: 0 !important; +} + +hr { + -webkit-box-sizing: content-box; + box-sizing: content-box; + height: 0; + overflow: visible; +} + +h1, h2, h3, h4, h5, h6 { + margin-top: 0; + margin-bottom: 0.5rem; +} + +p { + margin-top: 0; + margin-bottom: 1rem; +} + +abbr[title], +abbr[data-original-title] { + text-decoration: underline; + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + cursor: help; + border-bottom: 0; +} + +address { + margin-bottom: 1rem; + font-style: normal; + line-height: inherit; +} + +ol, +ul, +dl { + margin-top: 0; + margin-bottom: 1rem; +} + +ol ol, +ul ul, +ol ul, +ul ol { + margin-bottom: 0; +} + +dt { + font-weight: 700; +} + +dd { + margin-bottom: .5rem; + margin-left: 0; +} + +blockquote { + margin: 0 0 1rem; +} + +dfn { + font-style: italic; +} + +b, +strong { + font-weight: bolder; +} + +small { + font-size: 80%; +} + +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} + +sub { + bottom: -.25em; +} + +sup { + top: -.5em; +} + +a { + color: #2FA4E7; + text-decoration: none; + background-color: transparent; + -webkit-text-decoration-skip: objects; +} + +a:hover { + color: #157ab5; + text-decoration: underline; +} + +a:not([href]):not([tabindex]) { + color: inherit; + text-decoration: none; +} + +a:not([href]):not([tabindex]):focus, a:not([href]):not([tabindex]):hover { + color: inherit; + text-decoration: none; +} + +a:not([href]):not([tabindex]):focus { + outline: 0; +} + +pre, +code, +kbd, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +pre { + margin-top: 0; + margin-bottom: 1rem; + overflow: auto; + -ms-overflow-style: scrollbar; +} + +figure { + margin: 0 0 1rem; +} + +img { + vertical-align: middle; + border-style: none; +} + +svg:not(:root) { + overflow: hidden; +} + +a, +area, +button, +[role="button"], +input:not([type="range"]), +label, +select, +summary, +textarea { + -ms-touch-action: manipulation; + touch-action: manipulation; +} + +table { + border-collapse: collapse; +} + +caption { + padding-top: 0.75rem; + padding-bottom: 0.75rem; + color: #868e96; + text-align: left; + caption-side: bottom; +} + +th { + text-align: inherit; +} + +label { + display: inline-block; + margin-bottom: .5rem; +} + +button { + border-radius: 0; +} + +button:focus { + outline: 1px dotted; + outline: 5px auto -webkit-focus-ring-color; +} + +input, +button, +select, +optgroup, +textarea { + margin: 0; + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +button, +input { + overflow: visible; +} + +button, +select { + text-transform: none; +} + +button, +html [type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; +} + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + padding: 0; + border-style: none; +} + +input[type="radio"], +input[type="checkbox"] { + -webkit-box-sizing: border-box; + box-sizing: border-box; + padding: 0; +} + +input[type="date"], +input[type="time"], +input[type="datetime-local"], +input[type="month"] { + -webkit-appearance: listbox; +} + +textarea { + overflow: auto; + resize: vertical; +} + +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; +} + +legend { + display: block; + width: 100%; + max-width: 100%; + padding: 0; + margin-bottom: .5rem; + font-size: 1.5rem; + line-height: inherit; + color: inherit; + white-space: normal; +} + +progress { + vertical-align: baseline; +} + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +[type="search"] { + outline-offset: -2px; + -webkit-appearance: none; +} + +[type="search"]::-webkit-search-cancel-button, +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +::-webkit-file-upload-button { + font: inherit; + -webkit-appearance: button; +} + +output { + display: inline-block; +} + +summary { + display: list-item; + cursor: pointer; +} + +template { + display: none; +} + +[hidden] { + display: none !important; +} + +h1, h2, h3, h4, h5, h6, +.h1, .h2, .h3, .h4, .h5, .h6 { + margin-bottom: 0.5rem; + font-family: inherit; + font-weight: 500; + line-height: 1.2; + color: #2FA4E7; +} + +h1, .h1 { + font-size: 2.5rem; +} + +h2, .h2 { + font-size: 2rem; +} + +h3, .h3 { + font-size: 1.75rem; +} + +h4, .h4 { + font-size: 1.5rem; +} + +h5, .h5 { + font-size: 1.25rem; +} + +h6, .h6 { + font-size: 1rem; +} + +.lead { + font-size: 1.25rem; + font-weight: 300; +} + +.display-1 { + font-size: 6rem; + font-weight: 300; + line-height: 1.2; +} + +.display-2 { + font-size: 5.5rem; + font-weight: 300; + line-height: 1.2; +} + +.display-3 { + font-size: 4.5rem; + font-weight: 300; + line-height: 1.2; +} + +.display-4 { + font-size: 3.5rem; + font-weight: 300; + line-height: 1.2; +} + +hr { + margin-top: 1rem; + margin-bottom: 1rem; + border: 0; + border-top: 1px solid rgba(0, 0, 0, 0.1); +} + +small, +.small { + font-size: 80%; + font-weight: 400; +} + +mark, +.mark { + padding: 0.2em; + background-color: #fcf8e3; +} + +.list-unstyled { + padding-left: 0; + list-style: none; +} + +.list-inline { + padding-left: 0; + list-style: none; +} + +.list-inline-item { + display: inline-block; +} + +.list-inline-item:not(:last-child) { + margin-right: 0.5rem; +} + +.initialism { + font-size: 90%; + text-transform: uppercase; +} + +.blockquote { + margin-bottom: 1rem; + font-size: 1.25rem; +} + +.blockquote-footer { + display: block; + font-size: 80%; + color: #868e96; +} + +.blockquote-footer::before { + content: "\2014 \00A0"; +} + +.img-fluid { + max-width: 100%; + height: auto; +} + +.img-thumbnail { + padding: 0.25rem; + background-color: #fff; + border: 1px solid #dee2e6; + border-radius: 0.25rem; + max-width: 100%; + height: auto; +} + +.figure { + display: inline-block; +} + +.figure-img { + margin-bottom: 0.5rem; + line-height: 1; +} + +.figure-caption { + font-size: 90%; + color: #868e96; +} + +code, +kbd, +pre, +samp { + font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} + +code { + font-size: 87.5%; + color: #e83e8c; + word-break: break-word; +} + +a > code { + color: inherit; +} + +kbd { + padding: 0.2rem 0.4rem; + font-size: 87.5%; + color: #fff; + background-color: #212529; + border-radius: 0.2rem; +} + +kbd kbd { + padding: 0; + font-size: 100%; + font-weight: 700; +} + +pre { + display: block; + font-size: 87.5%; + color: #212529; +} + +pre code { + font-size: inherit; + color: inherit; + word-break: normal; +} + +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} + +.container { + width: 100%; + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} + +@media (min-width: 576px) { + .container { + max-width: 540px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 720px; + } +} + +@media (min-width: 992px) { + .container { + max-width: 960px; + } +} + +@media (min-width: 1200px) { + .container { + max-width: 1140px; + } +} + +.container-fluid { + width: 100%; + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} + +.row { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + margin-right: -15px; + margin-left: -15px; +} + +.no-gutters { + margin-right: 0; + margin-left: 0; +} + +.no-gutters > .col, +.no-gutters > [class*="col-"] { + padding-right: 0; + padding-left: 0; +} + +.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col, +.col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm, +.col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md, +.col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg, +.col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl, +.col-xl-auto { + position: relative; + width: 100%; + min-height: 1px; + padding-right: 15px; + padding-left: 15px; +} + +.col { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; +} + +.col-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; +} + +.col-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.3333333333%; + flex: 0 0 8.3333333333%; + max-width: 8.3333333333%; +} + +.col-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.6666666667%; + flex: 0 0 16.6666666667%; + max-width: 16.6666666667%; +} + +.col-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; +} + +.col-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.3333333333%; + flex: 0 0 33.3333333333%; + max-width: 33.3333333333%; +} + +.col-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.6666666667%; + flex: 0 0 41.6666666667%; + max-width: 41.6666666667%; +} + +.col-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; +} + +.col-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.3333333333%; + flex: 0 0 58.3333333333%; + max-width: 58.3333333333%; +} + +.col-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.6666666667%; + flex: 0 0 66.6666666667%; + max-width: 66.6666666667%; +} + +.col-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; +} + +.col-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.3333333333%; + flex: 0 0 83.3333333333%; + max-width: 83.3333333333%; +} + +.col-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.6666666667%; + flex: 0 0 91.6666666667%; + max-width: 91.6666666667%; +} + +.col-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; +} + +.order-first { + -webkit-box-ordinal-group: 0; + -ms-flex-order: -1; + order: -1; +} + +.order-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; +} + +.order-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; +} + +.order-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; +} + +.order-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; +} + +.order-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; +} + +.order-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; +} + +.order-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; +} + +.order-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; +} + +.order-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; +} + +.order-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; +} + +.order-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; +} + +.order-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; +} + +.offset-1 { + margin-left: 8.3333333333%; +} + +.offset-2 { + margin-left: 16.6666666667%; +} + +.offset-3 { + margin-left: 25%; +} + +.offset-4 { + margin-left: 33.3333333333%; +} + +.offset-5 { + margin-left: 41.6666666667%; +} + +.offset-6 { + margin-left: 50%; +} + +.offset-7 { + margin-left: 58.3333333333%; +} + +.offset-8 { + margin-left: 66.6666666667%; +} + +.offset-9 { + margin-left: 75%; +} + +.offset-10 { + margin-left: 83.3333333333%; +} + +.offset-11 { + margin-left: 91.6666666667%; +} + +@media (min-width: 576px) { + .col-sm { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .col-sm-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; + } + .col-sm-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.3333333333%; + flex: 0 0 8.3333333333%; + max-width: 8.3333333333%; + } + .col-sm-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.6666666667%; + flex: 0 0 16.6666666667%; + max-width: 16.6666666667%; + } + .col-sm-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-sm-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.3333333333%; + flex: 0 0 33.3333333333%; + max-width: 33.3333333333%; + } + .col-sm-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.6666666667%; + flex: 0 0 41.6666666667%; + max-width: 41.6666666667%; + } + .col-sm-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-sm-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.3333333333%; + flex: 0 0 58.3333333333%; + max-width: 58.3333333333%; + } + .col-sm-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.6666666667%; + flex: 0 0 66.6666666667%; + max-width: 66.6666666667%; + } + .col-sm-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-sm-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.3333333333%; + flex: 0 0 83.3333333333%; + max-width: 83.3333333333%; + } + .col-sm-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.6666666667%; + flex: 0 0 91.6666666667%; + max-width: 91.6666666667%; + } + .col-sm-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-sm-first { + -webkit-box-ordinal-group: 0; + -ms-flex-order: -1; + order: -1; + } + .order-sm-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; + } + .order-sm-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; + } + .order-sm-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; + } + .order-sm-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; + } + .order-sm-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; + } + .order-sm-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; + } + .order-sm-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; + } + .order-sm-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; + } + .order-sm-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; + } + .order-sm-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; + } + .order-sm-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; + } + .order-sm-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; + } + .offset-sm-0 { + margin-left: 0; + } + .offset-sm-1 { + margin-left: 8.3333333333%; + } + .offset-sm-2 { + margin-left: 16.6666666667%; + } + .offset-sm-3 { + margin-left: 25%; + } + .offset-sm-4 { + margin-left: 33.3333333333%; + } + .offset-sm-5 { + margin-left: 41.6666666667%; + } + .offset-sm-6 { + margin-left: 50%; + } + .offset-sm-7 { + margin-left: 58.3333333333%; + } + .offset-sm-8 { + margin-left: 66.6666666667%; + } + .offset-sm-9 { + margin-left: 75%; + } + .offset-sm-10 { + margin-left: 83.3333333333%; + } + .offset-sm-11 { + margin-left: 91.6666666667%; + } +} + +@media (min-width: 768px) { + .col-md { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .col-md-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; + } + .col-md-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.3333333333%; + flex: 0 0 8.3333333333%; + max-width: 8.3333333333%; + } + .col-md-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.6666666667%; + flex: 0 0 16.6666666667%; + max-width: 16.6666666667%; + } + .col-md-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-md-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.3333333333%; + flex: 0 0 33.3333333333%; + max-width: 33.3333333333%; + } + .col-md-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.6666666667%; + flex: 0 0 41.6666666667%; + max-width: 41.6666666667%; + } + .col-md-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-md-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.3333333333%; + flex: 0 0 58.3333333333%; + max-width: 58.3333333333%; + } + .col-md-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.6666666667%; + flex: 0 0 66.6666666667%; + max-width: 66.6666666667%; + } + .col-md-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-md-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.3333333333%; + flex: 0 0 83.3333333333%; + max-width: 83.3333333333%; + } + .col-md-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.6666666667%; + flex: 0 0 91.6666666667%; + max-width: 91.6666666667%; + } + .col-md-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-md-first { + -webkit-box-ordinal-group: 0; + -ms-flex-order: -1; + order: -1; + } + .order-md-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; + } + .order-md-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; + } + .order-md-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; + } + .order-md-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; + } + .order-md-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; + } + .order-md-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; + } + .order-md-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; + } + .order-md-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; + } + .order-md-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; + } + .order-md-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; + } + .order-md-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; + } + .order-md-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; + } + .offset-md-0 { + margin-left: 0; + } + .offset-md-1 { + margin-left: 8.3333333333%; + } + .offset-md-2 { + margin-left: 16.6666666667%; + } + .offset-md-3 { + margin-left: 25%; + } + .offset-md-4 { + margin-left: 33.3333333333%; + } + .offset-md-5 { + margin-left: 41.6666666667%; + } + .offset-md-6 { + margin-left: 50%; + } + .offset-md-7 { + margin-left: 58.3333333333%; + } + .offset-md-8 { + margin-left: 66.6666666667%; + } + .offset-md-9 { + margin-left: 75%; + } + .offset-md-10 { + margin-left: 83.3333333333%; + } + .offset-md-11 { + margin-left: 91.6666666667%; + } +} + +@media (min-width: 992px) { + .col-lg { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .col-lg-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; + } + .col-lg-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.3333333333%; + flex: 0 0 8.3333333333%; + max-width: 8.3333333333%; + } + .col-lg-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.6666666667%; + flex: 0 0 16.6666666667%; + max-width: 16.6666666667%; + } + .col-lg-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-lg-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.3333333333%; + flex: 0 0 33.3333333333%; + max-width: 33.3333333333%; + } + .col-lg-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.6666666667%; + flex: 0 0 41.6666666667%; + max-width: 41.6666666667%; + } + .col-lg-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-lg-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.3333333333%; + flex: 0 0 58.3333333333%; + max-width: 58.3333333333%; + } + .col-lg-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.6666666667%; + flex: 0 0 66.6666666667%; + max-width: 66.6666666667%; + } + .col-lg-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-lg-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.3333333333%; + flex: 0 0 83.3333333333%; + max-width: 83.3333333333%; + } + .col-lg-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.6666666667%; + flex: 0 0 91.6666666667%; + max-width: 91.6666666667%; + } + .col-lg-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-lg-first { + -webkit-box-ordinal-group: 0; + -ms-flex-order: -1; + order: -1; + } + .order-lg-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; + } + .order-lg-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; + } + .order-lg-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; + } + .order-lg-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; + } + .order-lg-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; + } + .order-lg-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; + } + .order-lg-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; + } + .order-lg-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; + } + .order-lg-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; + } + .order-lg-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; + } + .order-lg-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; + } + .order-lg-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; + } + .offset-lg-0 { + margin-left: 0; + } + .offset-lg-1 { + margin-left: 8.3333333333%; + } + .offset-lg-2 { + margin-left: 16.6666666667%; + } + .offset-lg-3 { + margin-left: 25%; + } + .offset-lg-4 { + margin-left: 33.3333333333%; + } + .offset-lg-5 { + margin-left: 41.6666666667%; + } + .offset-lg-6 { + margin-left: 50%; + } + .offset-lg-7 { + margin-left: 58.3333333333%; + } + .offset-lg-8 { + margin-left: 66.6666666667%; + } + .offset-lg-9 { + margin-left: 75%; + } + .offset-lg-10 { + margin-left: 83.3333333333%; + } + .offset-lg-11 { + margin-left: 91.6666666667%; + } +} + +@media (min-width: 1200px) { + .col-xl { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .col-xl-auto { + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: none; + } + .col-xl-1 { + -webkit-box-flex: 0; + -ms-flex: 0 0 8.3333333333%; + flex: 0 0 8.3333333333%; + max-width: 8.3333333333%; + } + .col-xl-2 { + -webkit-box-flex: 0; + -ms-flex: 0 0 16.6666666667%; + flex: 0 0 16.6666666667%; + max-width: 16.6666666667%; + } + .col-xl-3 { + -webkit-box-flex: 0; + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-xl-4 { + -webkit-box-flex: 0; + -ms-flex: 0 0 33.3333333333%; + flex: 0 0 33.3333333333%; + max-width: 33.3333333333%; + } + .col-xl-5 { + -webkit-box-flex: 0; + -ms-flex: 0 0 41.6666666667%; + flex: 0 0 41.6666666667%; + max-width: 41.6666666667%; + } + .col-xl-6 { + -webkit-box-flex: 0; + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-xl-7 { + -webkit-box-flex: 0; + -ms-flex: 0 0 58.3333333333%; + flex: 0 0 58.3333333333%; + max-width: 58.3333333333%; + } + .col-xl-8 { + -webkit-box-flex: 0; + -ms-flex: 0 0 66.6666666667%; + flex: 0 0 66.6666666667%; + max-width: 66.6666666667%; + } + .col-xl-9 { + -webkit-box-flex: 0; + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-xl-10 { + -webkit-box-flex: 0; + -ms-flex: 0 0 83.3333333333%; + flex: 0 0 83.3333333333%; + max-width: 83.3333333333%; + } + .col-xl-11 { + -webkit-box-flex: 0; + -ms-flex: 0 0 91.6666666667%; + flex: 0 0 91.6666666667%; + max-width: 91.6666666667%; + } + .col-xl-12 { + -webkit-box-flex: 0; + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-xl-first { + -webkit-box-ordinal-group: 0; + -ms-flex-order: -1; + order: -1; + } + .order-xl-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; + } + .order-xl-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; + } + .order-xl-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3; + } + .order-xl-4 { + -webkit-box-ordinal-group: 5; + -ms-flex-order: 4; + order: 4; + } + .order-xl-5 { + -webkit-box-ordinal-group: 6; + -ms-flex-order: 5; + order: 5; + } + .order-xl-6 { + -webkit-box-ordinal-group: 7; + -ms-flex-order: 6; + order: 6; + } + .order-xl-7 { + -webkit-box-ordinal-group: 8; + -ms-flex-order: 7; + order: 7; + } + .order-xl-8 { + -webkit-box-ordinal-group: 9; + -ms-flex-order: 8; + order: 8; + } + .order-xl-9 { + -webkit-box-ordinal-group: 10; + -ms-flex-order: 9; + order: 9; + } + .order-xl-10 { + -webkit-box-ordinal-group: 11; + -ms-flex-order: 10; + order: 10; + } + .order-xl-11 { + -webkit-box-ordinal-group: 12; + -ms-flex-order: 11; + order: 11; + } + .order-xl-12 { + -webkit-box-ordinal-group: 13; + -ms-flex-order: 12; + order: 12; + } + .offset-xl-0 { + margin-left: 0; + } + .offset-xl-1 { + margin-left: 8.3333333333%; + } + .offset-xl-2 { + margin-left: 16.6666666667%; + } + .offset-xl-3 { + margin-left: 25%; + } + .offset-xl-4 { + margin-left: 33.3333333333%; + } + .offset-xl-5 { + margin-left: 41.6666666667%; + } + .offset-xl-6 { + margin-left: 50%; + } + .offset-xl-7 { + margin-left: 58.3333333333%; + } + .offset-xl-8 { + margin-left: 66.6666666667%; + } + .offset-xl-9 { + margin-left: 75%; + } + .offset-xl-10 { + margin-left: 83.3333333333%; + } + .offset-xl-11 { + margin-left: 91.6666666667%; + } +} + +.table { + width: 100%; + max-width: 100%; + margin-bottom: 1rem; + background-color: transparent; +} + +.table th, +.table td { + padding: 0.75rem; + vertical-align: top; + border-top: 1px solid #dee2e6; +} + +.table thead th { + vertical-align: bottom; + border-bottom: 2px solid #dee2e6; +} + +.table tbody + tbody { + border-top: 2px solid #dee2e6; +} + +.table .table { + background-color: #fff; +} + +.table-sm th, +.table-sm td { + padding: 0.3rem; +} + +.table-bordered { + border: 1px solid #dee2e6; +} + +.table-bordered th, +.table-bordered td { + border: 1px solid #dee2e6; +} + +.table-bordered thead th, +.table-bordered thead td { + border-bottom-width: 2px; +} + +.table-striped tbody tr:nth-of-type(odd) { + background-color: rgba(0, 0, 0, 0.05); +} + +.table-hover tbody tr:hover { + background-color: rgba(0, 0, 0, 0.075); +} + +.table-primary, +.table-primary > th, +.table-primary > td { + background-color: #c5e6f8; +} + +.table-hover .table-primary:hover { + background-color: #aedcf5; +} + +.table-hover .table-primary:hover > td, +.table-hover .table-primary:hover > th { + background-color: #aedcf5; +} + +.table-secondary, +.table-secondary > th, +.table-secondary > td { + background-color: #f9fafb; +} + +.table-hover .table-secondary:hover { + background-color: #eaedf1; +} + +.table-hover .table-secondary:hover > td, +.table-hover .table-secondary:hover > th { + background-color: #eaedf1; +} + +.table-success, +.table-success > th, +.table-success > td { + background-color: #d8e7c8; +} + +.table-hover .table-success:hover { + background-color: #cbdfb6; +} + +.table-hover .table-success:hover > td, +.table-hover .table-success:hover > th { + background-color: #cbdfb6; +} + +.table-info, +.table-info > th, +.table-info > td { + background-color: #b8c8d8; +} + +.table-hover .table-info:hover { + background-color: #a8bbcf; +} + +.table-hover .table-info:hover > td, +.table-hover .table-info:hover > th { + background-color: #a8bbcf; +} + +.table-warning, +.table-warning > th, +.table-warning > td { + background-color: #f5d0b8; +} + +.table-hover .table-warning:hover { + background-color: #f2c1a2; +} + +.table-hover .table-warning:hover > td, +.table-hover .table-warning:hover > th { + background-color: #f2c1a2; +} + +.table-danger, +.table-danger > th, +.table-danger > td { + background-color: #efbfc1; +} + +.table-hover .table-danger:hover { + background-color: #eaabad; +} + +.table-hover .table-danger:hover > td, +.table-hover .table-danger:hover > th { + background-color: #eaabad; +} + +.table-light, +.table-light > th, +.table-light > td { + background-color: #fdfdfe; +} + +.table-hover .table-light:hover { + background-color: #ececf6; +} + +.table-hover .table-light:hover > td, +.table-hover .table-light:hover > th { + background-color: #ececf6; +} + +.table-dark, +.table-dark > th, +.table-dark > td { + background-color: #c6c8ca; +} + +.table-hover .table-dark:hover { + background-color: #b9bbbe; +} + +.table-hover .table-dark:hover > td, +.table-hover .table-dark:hover > th { + background-color: #b9bbbe; +} + +.table-active, +.table-active > th, +.table-active > td { + background-color: rgba(0, 0, 0, 0.075); +} + +.table-hover .table-active:hover { + background-color: rgba(0, 0, 0, 0.075); +} + +.table-hover .table-active:hover > td, +.table-hover .table-active:hover > th { + background-color: rgba(0, 0, 0, 0.075); +} + +.table .thead-dark th { + color: #fff; + background-color: #212529; + border-color: #32383e; +} + +.table .thead-light th { + color: #495057; + background-color: #e9ecef; + border-color: #dee2e6; +} + +.table-dark { + color: #fff; + background-color: #212529; +} + +.table-dark th, +.table-dark td, +.table-dark thead th { + border-color: #32383e; +} + +.table-dark.table-bordered { + border: 0; +} + +.table-dark.table-striped tbody tr:nth-of-type(odd) { + background-color: rgba(255, 255, 255, 0.05); +} + +.table-dark.table-hover tbody tr:hover { + background-color: rgba(255, 255, 255, 0.075); +} + +@media (max-width: 575.99px) { + .table-responsive-sm { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; + } + .table-responsive-sm > .table-bordered { + border: 0; + } +} + +@media (max-width: 767.99px) { + .table-responsive-md { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; + } + .table-responsive-md > .table-bordered { + border: 0; + } +} + +@media (max-width: 991.99px) { + .table-responsive-lg { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; + } + .table-responsive-lg > .table-bordered { + border: 0; + } +} + +@media (max-width: 1199.99px) { + .table-responsive-xl { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; + } + .table-responsive-xl > .table-bordered { + border: 0; + } +} + +.table-responsive { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; +} + +.table-responsive > .table-bordered { + border: 0; +} + +.form-control { + display: block; + width: 100%; + padding: 0.375rem 0.75rem; + font-size: 1rem; + line-height: 1.5; + color: #495057; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #ced4da; + border-radius: 0.25rem; + -webkit-transition: border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + transition: border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; +} + +.form-control::-ms-expand { + background-color: transparent; + border: 0; +} + +.form-control:focus { + color: #495057; + background-color: #fff; + border-color: #a1d6f4; + outline: 0; + -webkit-box-shadow: 0 0 0 0.2rem rgba(47, 164, 231, 0.25); + box-shadow: 0 0 0 0.2rem rgba(47, 164, 231, 0.25); +} + +.form-control::-webkit-input-placeholder { + color: #868e96; + opacity: 1; +} + +.form-control:-ms-input-placeholder { + color: #868e96; + opacity: 1; +} + +.form-control::-ms-input-placeholder { + color: #868e96; + opacity: 1; +} + +.form-control::placeholder { + color: #868e96; + opacity: 1; +} + +.form-control:disabled, .form-control[readonly] { + background-color: #e9ecef; + opacity: 1; +} + +select.form-control:not([size]):not([multiple]) { + height: calc(2.25rem + 2px); +} + +select.form-control:focus::-ms-value { + color: #495057; + background-color: #fff; +} + +.form-control-file, +.form-control-range { + display: block; + width: 100%; +} + +.col-form-label { + padding-top: calc(0.375rem + 1px); + padding-bottom: calc(0.375rem + 1px); + margin-bottom: 0; + font-size: inherit; + line-height: 1.5; +} + +.col-form-label-lg { + padding-top: calc(0.5rem + 1px); + padding-bottom: calc(0.5rem + 1px); + font-size: 1.25rem; + line-height: 1.5; +} + +.col-form-label-sm { + padding-top: calc(0.25rem + 1px); + padding-bottom: calc(0.25rem + 1px); + font-size: 0.875rem; + line-height: 1.5; +} + +.form-control-plaintext { + display: block; + width: 100%; + padding-top: 0.375rem; + padding-bottom: 0.375rem; + margin-bottom: 0; + line-height: 1.5; + background-color: transparent; + border: solid transparent; + border-width: 1px 0; +} + +.form-control-plaintext.form-control-sm, .input-group-sm > .form-control-plaintext.form-control, +.input-group-sm > .input-group-prepend > .form-control-plaintext.input-group-text, +.input-group-sm > .input-group-append > .form-control-plaintext.input-group-text, +.input-group-sm > .input-group-prepend > .form-control-plaintext.btn, +.input-group-sm > .input-group-append > .form-control-plaintext.btn, .form-control-plaintext.form-control-lg, .input-group-lg > .form-control-plaintext.form-control, +.input-group-lg > .input-group-prepend > .form-control-plaintext.input-group-text, +.input-group-lg > .input-group-append > .form-control-plaintext.input-group-text, +.input-group-lg > .input-group-prepend > .form-control-plaintext.btn, +.input-group-lg > .input-group-append > .form-control-plaintext.btn { + padding-right: 0; + padding-left: 0; +} + +.form-control-sm, .input-group-sm > .form-control, +.input-group-sm > .input-group-prepend > .input-group-text, +.input-group-sm > .input-group-append > .input-group-text, +.input-group-sm > .input-group-prepend > .btn, +.input-group-sm > .input-group-append > .btn { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; + border-radius: 0.2rem; +} + +select.form-control-sm:not([size]):not([multiple]), .input-group-sm > select.form-control:not([size]):not([multiple]), +.input-group-sm > .input-group-prepend > select.input-group-text:not([size]):not([multiple]), +.input-group-sm > .input-group-append > select.input-group-text:not([size]):not([multiple]), +.input-group-sm > .input-group-prepend > select.btn:not([size]):not([multiple]), +.input-group-sm > .input-group-append > select.btn:not([size]):not([multiple]) { + height: calc(1.8125rem + 2px); +} + +.form-control-lg, .input-group-lg > .form-control, +.input-group-lg > .input-group-prepend > .input-group-text, +.input-group-lg > .input-group-append > .input-group-text, +.input-group-lg > .input-group-prepend > .btn, +.input-group-lg > .input-group-append > .btn { + padding: 0.5rem 1rem; + font-size: 1.25rem; + line-height: 1.5; + border-radius: 0.3rem; +} + +select.form-control-lg:not([size]):not([multiple]), .input-group-lg > select.form-control:not([size]):not([multiple]), +.input-group-lg > .input-group-prepend > select.input-group-text:not([size]):not([multiple]), +.input-group-lg > .input-group-append > select.input-group-text:not([size]):not([multiple]), +.input-group-lg > .input-group-prepend > select.btn:not([size]):not([multiple]), +.input-group-lg > .input-group-append > select.btn:not([size]):not([multiple]) { + height: calc(2.875rem + 2px); +} + +.form-group { + margin-bottom: 1rem; +} + +.form-text { + display: block; + margin-top: 0.25rem; +} + +.form-row { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + margin-right: -5px; + margin-left: -5px; +} + +.form-row > .col, +.form-row > [class*="col-"] { + padding-right: 5px; + padding-left: 5px; +} + +.form-check { + position: relative; + display: block; + padding-left: 1.25rem; +} + +.form-check-input { + position: absolute; + margin-top: 0.3rem; + margin-left: -1.25rem; +} + +.form-check-input:disabled ~ .form-check-label { + color: #868e96; +} + +.form-check-label { + margin-bottom: 0; +} + +.form-check-inline { + display: -webkit-inline-box; + display: -ms-inline-flexbox; + display: inline-flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + padding-left: 0; + margin-right: 0.75rem; +} + +.form-check-inline .form-check-input { + position: static; + margin-top: 0; + margin-right: 0.3125rem; + margin-left: 0; +} + +.valid-feedback { + display: none; + width: 100%; + margin-top: 0.25rem; + font-size: 80%; + color: #73A839; +} + +.valid-tooltip { + position: absolute; + top: 100%; + z-index: 5; + display: none; + width: 250px; + padding: .5rem; + margin-top: .1rem; + font-size: .875rem; + line-height: 1; + color: #fff; + background-color: rgba(115, 168, 57, 0.8); + border-radius: .2rem; +} + +.was-validated .form-control:valid, .form-control.is-valid, .was-validated +.custom-select:valid, +.custom-select.is-valid { + border-color: #73A839; +} + +.was-validated .form-control:valid:focus, .form-control.is-valid:focus, .was-validated +.custom-select:valid:focus, +.custom-select.is-valid:focus { + border-color: #73A839; + -webkit-box-shadow: 0 0 0 0.2rem rgba(115, 168, 57, 0.25); + box-shadow: 0 0 0 0.2rem rgba(115, 168, 57, 0.25); +} + +.was-validated .form-control:valid ~ .valid-feedback, +.was-validated .form-control:valid ~ .valid-tooltip, .form-control.is-valid ~ .valid-feedback, +.form-control.is-valid ~ .valid-tooltip, .was-validated +.custom-select:valid ~ .valid-feedback, +.was-validated +.custom-select:valid ~ .valid-tooltip, +.custom-select.is-valid ~ .valid-feedback, +.custom-select.is-valid ~ .valid-tooltip { + display: block; +} + +.was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label { + color: #73A839; +} + +.was-validated .custom-control-input:valid ~ .custom-control-label, .custom-control-input.is-valid ~ .custom-control-label { + color: #73A839; +} + +.was-validated .custom-control-input:valid ~ .custom-control-label::before, .custom-control-input.is-valid ~ .custom-control-label::before { + background-color: #b2d789; +} + +.was-validated .custom-control-input:valid ~ .valid-feedback, +.was-validated .custom-control-input:valid ~ .valid-tooltip, .custom-control-input.is-valid ~ .valid-feedback, +.custom-control-input.is-valid ~ .valid-tooltip { + display: block; +} + +.was-validated .custom-control-input:valid:checked ~ .custom-control-label::before, .custom-control-input.is-valid:checked ~ .custom-control-label::before { + background-color: #8dc450; +} + +.was-validated .custom-control-input:valid:focus ~ .custom-control-label::before, .custom-control-input.is-valid:focus ~ .custom-control-label::before { + -webkit-box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(115, 168, 57, 0.25); + box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(115, 168, 57, 0.25); +} + +.was-validated .custom-file-input:valid ~ .custom-file-label, .custom-file-input.is-valid ~ .custom-file-label { + border-color: #73A839; +} + +.was-validated .custom-file-input:valid ~ .custom-file-label::before, .custom-file-input.is-valid ~ .custom-file-label::before { + border-color: inherit; +} + +.was-validated .custom-file-input:valid ~ .valid-feedback, +.was-validated .custom-file-input:valid ~ .valid-tooltip, .custom-file-input.is-valid ~ .valid-feedback, +.custom-file-input.is-valid ~ .valid-tooltip { + display: block; +} + +.was-validated .custom-file-input:valid:focus ~ .custom-file-label, .custom-file-input.is-valid:focus ~ .custom-file-label { + -webkit-box-shadow: 0 0 0 0.2rem rgba(115, 168, 57, 0.25); + box-shadow: 0 0 0 0.2rem rgba(115, 168, 57, 0.25); +} + +.invalid-feedback { + display: none; + width: 100%; + margin-top: 0.25rem; + font-size: 80%; + color: #C71C22; +} + +.invalid-tooltip { + position: absolute; + top: 100%; + z-index: 5; + display: none; + width: 250px; + padding: .5rem; + margin-top: .1rem; + font-size: .875rem; + line-height: 1; + color: #fff; + background-color: rgba(199, 28, 34, 0.8); + border-radius: .2rem; +} + +.was-validated .form-control:invalid, .form-control.is-invalid, .was-validated +.custom-select:invalid, +.custom-select.is-invalid { + border-color: #C71C22; +} + +.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus, .was-validated +.custom-select:invalid:focus, +.custom-select.is-invalid:focus { + border-color: #C71C22; + -webkit-box-shadow: 0 0 0 0.2rem rgba(199, 28, 34, 0.25); + box-shadow: 0 0 0 0.2rem rgba(199, 28, 34, 0.25); +} + +.was-validated .form-control:invalid ~ .invalid-feedback, +.was-validated .form-control:invalid ~ .invalid-tooltip, .form-control.is-invalid ~ .invalid-feedback, +.form-control.is-invalid ~ .invalid-tooltip, .was-validated +.custom-select:invalid ~ .invalid-feedback, +.was-validated +.custom-select:invalid ~ .invalid-tooltip, +.custom-select.is-invalid ~ .invalid-feedback, +.custom-select.is-invalid ~ .invalid-tooltip { + display: block; +} + +.was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label { + color: #C71C22; +} + +.was-validated .custom-control-input:invalid ~ .custom-control-label, .custom-control-input.is-invalid ~ .custom-control-label { + color: #C71C22; +} + +.was-validated .custom-control-input:invalid ~ .custom-control-label::before, .custom-control-input.is-invalid ~ .custom-control-label::before { + background-color: #ec777b; +} + +.was-validated .custom-control-input:invalid ~ .invalid-feedback, +.was-validated .custom-control-input:invalid ~ .invalid-tooltip, .custom-control-input.is-invalid ~ .invalid-feedback, +.custom-control-input.is-invalid ~ .invalid-tooltip { + display: block; +} + +.was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before, .custom-control-input.is-invalid:checked ~ .custom-control-label::before { + background-color: #e2343a; +} + +.was-validated .custom-control-input:invalid:focus ~ .custom-control-label::before, .custom-control-input.is-invalid:focus ~ .custom-control-label::before { + -webkit-box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(199, 28, 34, 0.25); + box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(199, 28, 34, 0.25); +} + +.was-validated .custom-file-input:invalid ~ .custom-file-label, .custom-file-input.is-invalid ~ .custom-file-label { + border-color: #C71C22; +} + +.was-validated .custom-file-input:invalid ~ .custom-file-label::before, .custom-file-input.is-invalid ~ .custom-file-label::before { + border-color: inherit; +} + +.was-validated .custom-file-input:invalid ~ .invalid-feedback, +.was-validated .custom-file-input:invalid ~ .invalid-tooltip, .custom-file-input.is-invalid ~ .invalid-feedback, +.custom-file-input.is-invalid ~ .invalid-tooltip { + display: block; +} + +.was-validated .custom-file-input:invalid:focus ~ .custom-file-label, .custom-file-input.is-invalid:focus ~ .custom-file-label { + -webkit-box-shadow: 0 0 0 0.2rem rgba(199, 28, 34, 0.25); + box-shadow: 0 0 0 0.2rem rgba(199, 28, 34, 0.25); +} + +.form-inline { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row wrap; + flex-flow: row wrap; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.form-inline .form-check { + width: 100%; +} + +@media (min-width: 576px) { + .form-inline label { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + margin-bottom: 0; + } + .form-inline .form-group { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row wrap; + flex-flow: row wrap; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + margin-bottom: 0; + } + .form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .form-inline .form-control-plaintext { + display: inline-block; + } + .form-inline .input-group { + width: auto; + } + .form-inline .form-check { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + width: auto; + padding-left: 0; + } + .form-inline .form-check-input { + position: relative; + margin-top: 0; + margin-right: 0.25rem; + margin-left: 0; + } + .form-inline .custom-control { + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + } + .form-inline .custom-control-label { + margin-bottom: 0; + } +} + +.btn { + display: inline-block; + font-weight: 400; + text-align: center; + white-space: nowrap; + vertical-align: middle; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + border: 1px solid transparent; + padding: 0.375rem 0.75rem; + font-size: 1rem; + line-height: 1.5; + border-radius: 0.25rem; + -webkit-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out; +} + +.btn:focus, .btn:hover { + text-decoration: none; +} + +.btn:focus, .btn.focus { + outline: 0; + -webkit-box-shadow: 0 0 0 0.2rem rgba(47, 164, 231, 0.25); + box-shadow: 0 0 0 0.2rem rgba(47, 164, 231, 0.25); +} + +.btn.disabled, .btn:disabled { + opacity: 0.65; +} + +.btn:not([disabled]):not(.disabled) { + cursor: pointer; +} + +.btn:not([disabled]):not(.disabled):active, .btn:not([disabled]):not(.disabled).active { + background-image: none; +} + +a.btn.disabled, +fieldset[disabled] a.btn { + pointer-events: none; +} + +.btn-primary { + color: #fff; + background-color: #2FA4E7; + border-color: #2FA4E7; +} + +.btn-primary:hover { + color: #fff; + background-color: #1992d7; + border-color: #178acc; +} + +.btn-primary:focus, .btn-primary.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(47, 164, 231, 0.5); + box-shadow: 0 0 0 0.2rem rgba(47, 164, 231, 0.5); +} + +.btn-primary.disabled, .btn-primary:disabled { + background-color: #2FA4E7; + border-color: #2FA4E7; +} + +.btn-primary:not([disabled]):not(.disabled):active, .btn-primary:not([disabled]):not(.disabled).active, +.show > .btn-primary.dropdown-toggle { + color: #fff; + background-color: #178acc; + border-color: #1682c0; +} + +.btn-primary:not([disabled]):not(.disabled):active:focus, .btn-primary:not([disabled]):not(.disabled).active:focus, +.show > .btn-primary.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(47, 164, 231, 0.5); + box-shadow: 0 0 0 0.2rem rgba(47, 164, 231, 0.5); +} + +.btn-secondary { + color: #212529; + background-color: #e9ecef; + border-color: #e9ecef; +} + +.btn-secondary:hover { + color: #212529; + background-color: #d3d9df; + border-color: #cbd3da; +} + +.btn-secondary:focus, .btn-secondary.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(233, 236, 239, 0.5); + box-shadow: 0 0 0 0.2rem rgba(233, 236, 239, 0.5); +} + +.btn-secondary.disabled, .btn-secondary:disabled { + background-color: #e9ecef; + border-color: #e9ecef; +} + +.btn-secondary:not([disabled]):not(.disabled):active, .btn-secondary:not([disabled]):not(.disabled).active, +.show > .btn-secondary.dropdown-toggle { + color: #212529; + background-color: #cbd3da; + border-color: #c4ccd4; +} + +.btn-secondary:not([disabled]):not(.disabled):active:focus, .btn-secondary:not([disabled]):not(.disabled).active:focus, +.show > .btn-secondary.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(233, 236, 239, 0.5); + box-shadow: 0 0 0 0.2rem rgba(233, 236, 239, 0.5); +} + +.btn-success { + color: #fff; + background-color: #73A839; + border-color: #73A839; +} + +.btn-success:hover { + color: #fff; + background-color: #5f8b2f; + border-color: #59822c; +} + +.btn-success:focus, .btn-success.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(115, 168, 57, 0.5); + box-shadow: 0 0 0 0.2rem rgba(115, 168, 57, 0.5); +} + +.btn-success.disabled, .btn-success:disabled { + background-color: #73A839; + border-color: #73A839; +} + +.btn-success:not([disabled]):not(.disabled):active, .btn-success:not([disabled]):not(.disabled).active, +.show > .btn-success.dropdown-toggle { + color: #fff; + background-color: #59822c; + border-color: #527829; +} + +.btn-success:not([disabled]):not(.disabled):active:focus, .btn-success:not([disabled]):not(.disabled).active:focus, +.show > .btn-success.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(115, 168, 57, 0.5); + box-shadow: 0 0 0 0.2rem rgba(115, 168, 57, 0.5); +} + +.btn-info { + color: #fff; + background-color: #033C73; + border-color: #033C73; +} + +.btn-info:hover { + color: #fff; + background-color: #02294e; + border-color: #022241; +} + +.btn-info:focus, .btn-info.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(3, 60, 115, 0.5); + box-shadow: 0 0 0 0.2rem rgba(3, 60, 115, 0.5); +} + +.btn-info.disabled, .btn-info:disabled { + background-color: #033C73; + border-color: #033C73; +} + +.btn-info:not([disabled]):not(.disabled):active, .btn-info:not([disabled]):not(.disabled).active, +.show > .btn-info.dropdown-toggle { + color: #fff; + background-color: #022241; + border-color: #011c35; +} + +.btn-info:not([disabled]):not(.disabled):active:focus, .btn-info:not([disabled]):not(.disabled).active:focus, +.show > .btn-info.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(3, 60, 115, 0.5); + box-shadow: 0 0 0 0.2rem rgba(3, 60, 115, 0.5); +} + +.btn-warning { + color: #fff; + background-color: #DD5600; + border-color: #DD5600; +} + +.btn-warning:hover { + color: #fff; + background-color: #b74700; + border-color: #aa4200; +} + +.btn-warning:focus, .btn-warning.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(221, 86, 0, 0.5); + box-shadow: 0 0 0 0.2rem rgba(221, 86, 0, 0.5); +} + +.btn-warning.disabled, .btn-warning:disabled { + background-color: #DD5600; + border-color: #DD5600; +} + +.btn-warning:not([disabled]):not(.disabled):active, .btn-warning:not([disabled]):not(.disabled).active, +.show > .btn-warning.dropdown-toggle { + color: #fff; + background-color: #aa4200; + border-color: #9d3d00; +} + +.btn-warning:not([disabled]):not(.disabled):active:focus, .btn-warning:not([disabled]):not(.disabled).active:focus, +.show > .btn-warning.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(221, 86, 0, 0.5); + box-shadow: 0 0 0 0.2rem rgba(221, 86, 0, 0.5); +} + +.btn-danger { + color: #fff; + background-color: #C71C22; + border-color: #C71C22; +} + +.btn-danger:hover { + color: #fff; + background-color: #a5171c; + border-color: #9a161a; +} + +.btn-danger:focus, .btn-danger.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(199, 28, 34, 0.5); + box-shadow: 0 0 0 0.2rem rgba(199, 28, 34, 0.5); +} + +.btn-danger.disabled, .btn-danger:disabled { + background-color: #C71C22; + border-color: #C71C22; +} + +.btn-danger:not([disabled]):not(.disabled):active, .btn-danger:not([disabled]):not(.disabled).active, +.show > .btn-danger.dropdown-toggle { + color: #fff; + background-color: #9a161a; + border-color: #8f1418; +} + +.btn-danger:not([disabled]):not(.disabled):active:focus, .btn-danger:not([disabled]):not(.disabled).active:focus, +.show > .btn-danger.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(199, 28, 34, 0.5); + box-shadow: 0 0 0 0.2rem rgba(199, 28, 34, 0.5); +} + +.btn-light { + color: #212529; + background-color: #f8f9fa; + border-color: #f8f9fa; +} + +.btn-light:hover { + color: #212529; + background-color: #e2e6ea; + border-color: #dae0e5; +} + +.btn-light:focus, .btn-light.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); +} + +.btn-light.disabled, .btn-light:disabled { + background-color: #f8f9fa; + border-color: #f8f9fa; +} + +.btn-light:not([disabled]):not(.disabled):active, .btn-light:not([disabled]):not(.disabled).active, +.show > .btn-light.dropdown-toggle { + color: #212529; + background-color: #dae0e5; + border-color: #d3d9df; +} + +.btn-light:not([disabled]):not(.disabled):active:focus, .btn-light:not([disabled]):not(.disabled).active:focus, +.show > .btn-light.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); +} + +.btn-dark { + color: #fff; + background-color: #343a40; + border-color: #343a40; +} + +.btn-dark:hover { + color: #fff; + background-color: #23272b; + border-color: #1d2124; +} + +.btn-dark:focus, .btn-dark.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); +} + +.btn-dark.disabled, .btn-dark:disabled { + background-color: #343a40; + border-color: #343a40; +} + +.btn-dark:not([disabled]):not(.disabled):active, .btn-dark:not([disabled]):not(.disabled).active, +.show > .btn-dark.dropdown-toggle { + color: #fff; + background-color: #1d2124; + border-color: #171a1d; +} + +.btn-dark:not([disabled]):not(.disabled):active:focus, .btn-dark:not([disabled]):not(.disabled).active:focus, +.show > .btn-dark.dropdown-toggle:focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); +} + +.btn-outline-primary { + color: #2FA4E7; + background-color: transparent; + background-image: none; + border-color: #2FA4E7; +} + +.btn-outline-primary:hover { + color: #fff; + background-color: #2FA4E7; + border-color: #2FA4E7; +} + +.btn-outline-primary:focus, .btn-outline-primary.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(47, 164, 231, 0.5); + box-shadow: 0 0 0 0.2rem rgba(47, 164, 231, 0.5); +} + +.btn-outline-primary.disabled, .btn-outline-primary:disabled { + color: #2FA4E7; + background-color: transparent; +} + +.btn-outline-primary:not([disabled]):not(.disabled):active, .btn-outline-primary:not([disabled]):not(.disabled).active, +.show > .btn-outline-primary.dropdown-toggle { + color: #212529; + background-color: #2FA4E7; + border-color: #2FA4E7; + -webkit-box-shadow: 0 0 0 0.2rem rgba(47, 164, 231, 0.5); + box-shadow: 0 0 0 0.2rem rgba(47, 164, 231, 0.5); +} + +.btn-outline-secondary { + color: #e9ecef; + background-color: transparent; + background-image: none; + border-color: #e9ecef; +} + +.btn-outline-secondary:hover { + color: #212529; + background-color: #e9ecef; + border-color: #e9ecef; +} + +.btn-outline-secondary:focus, .btn-outline-secondary.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(233, 236, 239, 0.5); + box-shadow: 0 0 0 0.2rem rgba(233, 236, 239, 0.5); +} + +.btn-outline-secondary.disabled, .btn-outline-secondary:disabled { + color: #e9ecef; + background-color: transparent; +} + +.btn-outline-secondary:not([disabled]):not(.disabled):active, .btn-outline-secondary:not([disabled]):not(.disabled).active, +.show > .btn-outline-secondary.dropdown-toggle { + color: #212529; + background-color: #e9ecef; + border-color: #e9ecef; + -webkit-box-shadow: 0 0 0 0.2rem rgba(233, 236, 239, 0.5); + box-shadow: 0 0 0 0.2rem rgba(233, 236, 239, 0.5); +} + +.btn-outline-success { + color: #73A839; + background-color: transparent; + background-image: none; + border-color: #73A839; +} + +.btn-outline-success:hover { + color: #fff; + background-color: #73A839; + border-color: #73A839; +} + +.btn-outline-success:focus, .btn-outline-success.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(115, 168, 57, 0.5); + box-shadow: 0 0 0 0.2rem rgba(115, 168, 57, 0.5); +} + +.btn-outline-success.disabled, .btn-outline-success:disabled { + color: #73A839; + background-color: transparent; +} + +.btn-outline-success:not([disabled]):not(.disabled):active, .btn-outline-success:not([disabled]):not(.disabled).active, +.show > .btn-outline-success.dropdown-toggle { + color: #212529; + background-color: #73A839; + border-color: #73A839; + -webkit-box-shadow: 0 0 0 0.2rem rgba(115, 168, 57, 0.5); + box-shadow: 0 0 0 0.2rem rgba(115, 168, 57, 0.5); +} + +.btn-outline-info { + color: #033C73; + background-color: transparent; + background-image: none; + border-color: #033C73; +} + +.btn-outline-info:hover { + color: #fff; + background-color: #033C73; + border-color: #033C73; +} + +.btn-outline-info:focus, .btn-outline-info.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(3, 60, 115, 0.5); + box-shadow: 0 0 0 0.2rem rgba(3, 60, 115, 0.5); +} + +.btn-outline-info.disabled, .btn-outline-info:disabled { + color: #033C73; + background-color: transparent; +} + +.btn-outline-info:not([disabled]):not(.disabled):active, .btn-outline-info:not([disabled]):not(.disabled).active, +.show > .btn-outline-info.dropdown-toggle { + color: #212529; + background-color: #033C73; + border-color: #033C73; + -webkit-box-shadow: 0 0 0 0.2rem rgba(3, 60, 115, 0.5); + box-shadow: 0 0 0 0.2rem rgba(3, 60, 115, 0.5); +} + +.btn-outline-warning { + color: #DD5600; + background-color: transparent; + background-image: none; + border-color: #DD5600; +} + +.btn-outline-warning:hover { + color: #fff; + background-color: #DD5600; + border-color: #DD5600; +} + +.btn-outline-warning:focus, .btn-outline-warning.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(221, 86, 0, 0.5); + box-shadow: 0 0 0 0.2rem rgba(221, 86, 0, 0.5); +} + +.btn-outline-warning.disabled, .btn-outline-warning:disabled { + color: #DD5600; + background-color: transparent; +} + +.btn-outline-warning:not([disabled]):not(.disabled):active, .btn-outline-warning:not([disabled]):not(.disabled).active, +.show > .btn-outline-warning.dropdown-toggle { + color: #212529; + background-color: #DD5600; + border-color: #DD5600; + -webkit-box-shadow: 0 0 0 0.2rem rgba(221, 86, 0, 0.5); + box-shadow: 0 0 0 0.2rem rgba(221, 86, 0, 0.5); +} + +.btn-outline-danger { + color: #C71C22; + background-color: transparent; + background-image: none; + border-color: #C71C22; +} + +.btn-outline-danger:hover { + color: #fff; + background-color: #C71C22; + border-color: #C71C22; +} + +.btn-outline-danger:focus, .btn-outline-danger.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(199, 28, 34, 0.5); + box-shadow: 0 0 0 0.2rem rgba(199, 28, 34, 0.5); +} + +.btn-outline-danger.disabled, .btn-outline-danger:disabled { + color: #C71C22; + background-color: transparent; +} + +.btn-outline-danger:not([disabled]):not(.disabled):active, .btn-outline-danger:not([disabled]):not(.disabled).active, +.show > .btn-outline-danger.dropdown-toggle { + color: #212529; + background-color: #C71C22; + border-color: #C71C22; + -webkit-box-shadow: 0 0 0 0.2rem rgba(199, 28, 34, 0.5); + box-shadow: 0 0 0 0.2rem rgba(199, 28, 34, 0.5); +} + +.btn-outline-light { + color: #f8f9fa; + background-color: transparent; + background-image: none; + border-color: #f8f9fa; +} + +.btn-outline-light:hover { + color: #212529; + background-color: #f8f9fa; + border-color: #f8f9fa; +} + +.btn-outline-light:focus, .btn-outline-light.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); +} + +.btn-outline-light.disabled, .btn-outline-light:disabled { + color: #f8f9fa; + background-color: transparent; +} + +.btn-outline-light:not([disabled]):not(.disabled):active, .btn-outline-light:not([disabled]):not(.disabled).active, +.show > .btn-outline-light.dropdown-toggle { + color: #fff; + background-color: #f8f9fa; + border-color: #f8f9fa; + -webkit-box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); +} + +.btn-outline-dark { + color: #343a40; + background-color: transparent; + background-image: none; + border-color: #343a40; +} + +.btn-outline-dark:hover { + color: #fff; + background-color: #343a40; + border-color: #343a40; +} + +.btn-outline-dark:focus, .btn-outline-dark.focus { + -webkit-box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); +} + +.btn-outline-dark.disabled, .btn-outline-dark:disabled { + color: #343a40; + background-color: transparent; +} + +.btn-outline-dark:not([disabled]):not(.disabled):active, .btn-outline-dark:not([disabled]):not(.disabled).active, +.show > .btn-outline-dark.dropdown-toggle { + color: #212529; + background-color: #343a40; + border-color: #343a40; + -webkit-box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); +} + +.btn-link { + font-weight: 400; + color: #2FA4E7; + background-color: transparent; +} + +.btn-link:hover { + color: #157ab5; + text-decoration: underline; + background-color: transparent; + border-color: transparent; +} + +.btn-link:focus, .btn-link.focus { + text-decoration: underline; + border-color: transparent; + -webkit-box-shadow: none; + box-shadow: none; +} + +.btn-link:disabled, .btn-link.disabled { + color: #868e96; +} + +.btn-lg, .btn-group-lg > .btn { + padding: 0.5rem 1rem; + font-size: 1.25rem; + line-height: 1.5; + border-radius: 0.3rem; +} + +.btn-sm, .btn-group-sm > .btn { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; + border-radius: 0.2rem; +} + +.btn-block { + display: block; + width: 100%; +} + +.btn-block + .btn-block { + margin-top: 0.5rem; +} + +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; +} + +.fade { + opacity: 0; + -webkit-transition: opacity 0.15s linear; + transition: opacity 0.15s linear; +} + +.fade.show { + opacity: 1; +} + +.collapse { + display: none; +} + +.collapse.show { + display: block; +} + +tr.collapse.show { + display: table-row; +} + +tbody.collapse.show { + display: table-row-group; +} + +.collapsing { + position: relative; + height: 0; + overflow: hidden; + -webkit-transition: height 0.35s ease; + transition: height 0.35s ease; +} + +.dropup, +.dropdown { + position: relative; +} + +.dropdown-toggle::after { + display: inline-block; + width: 0; + height: 0; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid; + border-right: 0.3em solid transparent; + border-bottom: 0; + border-left: 0.3em solid transparent; +} + +.dropdown-toggle:empty::after { + margin-left: 0; +} + +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 10rem; + padding: 0.5rem 0; + margin: 0.125rem 0 0; + font-size: 1rem; + color: #868e96; + text-align: left; + list-style: none; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 0.25rem; +} + +.dropup .dropdown-menu { + margin-top: 0; + margin-bottom: 0.125rem; +} + +.dropup .dropdown-toggle::after { + display: inline-block; + width: 0; + height: 0; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0; + border-right: 0.3em solid transparent; + border-bottom: 0.3em solid; + border-left: 0.3em solid transparent; +} + +.dropup .dropdown-toggle:empty::after { + margin-left: 0; +} + +.dropright .dropdown-menu { + margin-top: 0; + margin-left: 0.125rem; +} + +.dropright .dropdown-toggle::after { + display: inline-block; + width: 0; + height: 0; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid transparent; + border-bottom: 0.3em solid transparent; + border-left: 0.3em solid; +} + +.dropright .dropdown-toggle:empty::after { + margin-left: 0; +} + +.dropright .dropdown-toggle::after { + vertical-align: 0; +} + +.dropleft .dropdown-menu { + margin-top: 0; + margin-right: 0.125rem; +} + +.dropleft .dropdown-toggle::after { + display: inline-block; + width: 0; + height: 0; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; +} + +.dropleft .dropdown-toggle::after { + display: none; +} + +.dropleft .dropdown-toggle::before { + display: inline-block; + width: 0; + height: 0; + margin-right: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid transparent; + border-right: 0.3em solid; + border-bottom: 0.3em solid transparent; +} + +.dropleft .dropdown-toggle:empty::after { + margin-left: 0; +} + +.dropleft .dropdown-toggle::before { + vertical-align: 0; +} + +.dropdown-divider { + height: 0; + margin: 0.5rem 0; + overflow: hidden; + border-top: 1px solid #e9ecef; +} + +.dropdown-item { + display: block; + width: 100%; + padding: 0.25rem 1.5rem; + clear: both; + font-weight: 400; + color: #868e96; + text-align: inherit; + white-space: nowrap; + background-color: transparent; + border: 0; +} + +.dropdown-item:focus, .dropdown-item:hover { + color: #fff; + text-decoration: none; + background-color: #2FA4E7; +} + +.dropdown-item.active, .dropdown-item:active { + color: #fff; + text-decoration: none; + background-color: #2FA4E7; +} + +.dropdown-item.disabled, .dropdown-item:disabled { + color: #868e96; + background-color: transparent; +} + +.dropdown-menu.show { + display: block; +} + +.dropdown-header { + display: block; + padding: 0.5rem 1.5rem; + margin-bottom: 0; + font-size: 0.875rem; + color: #868e96; + white-space: nowrap; +} + +.btn-group, +.btn-group-vertical { + position: relative; + display: -webkit-inline-box; + display: -ms-inline-flexbox; + display: inline-flex; + vertical-align: middle; +} + +.btn-group > .btn, +.btn-group-vertical > .btn { + position: relative; + -webkit-box-flex: 0; + -ms-flex: 0 1 auto; + flex: 0 1 auto; +} + +.btn-group > .btn:hover, +.btn-group-vertical > .btn:hover { + z-index: 1; +} + +.btn-group > .btn:focus, .btn-group > .btn:active, .btn-group > .btn.active, +.btn-group-vertical > .btn:focus, +.btn-group-vertical > .btn:active, +.btn-group-vertical > .btn.active { + z-index: 1; +} + +.btn-group .btn + .btn, +.btn-group .btn + .btn-group, +.btn-group .btn-group + .btn, +.btn-group .btn-group + .btn-group, +.btn-group-vertical .btn + .btn, +.btn-group-vertical .btn + .btn-group, +.btn-group-vertical .btn-group + .btn, +.btn-group-vertical .btn-group + .btn-group { + margin-left: -1px; +} + +.btn-toolbar { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.btn-toolbar .input-group { + width: auto; +} + +.btn-group > .btn:first-child { + margin-left: 0; +} + +.btn-group > .btn:not(:last-child):not(.dropdown-toggle), +.btn-group > .btn-group:not(:last-child) > .btn { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.btn-group > .btn:not(:first-child), +.btn-group > .btn-group:not(:first-child) > .btn { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.dropdown-toggle-split { + padding-right: 0.5625rem; + padding-left: 0.5625rem; +} + +.dropdown-toggle-split::after { + margin-left: 0; +} + +.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split { + padding-right: 0.375rem; + padding-left: 0.375rem; +} + +.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split { + padding-right: 0.75rem; + padding-left: 0.75rem; +} + +.btn-group-vertical { + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-align: start; + -ms-flex-align: start; + align-items: flex-start; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; +} + +.btn-group-vertical .btn, +.btn-group-vertical .btn-group { + width: 100%; +} + +.btn-group-vertical > .btn + .btn, +.btn-group-vertical > .btn + .btn-group, +.btn-group-vertical > .btn-group + .btn, +.btn-group-vertical > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; +} + +.btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle), +.btn-group-vertical > .btn-group:not(:last-child) > .btn { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.btn-group-vertical > .btn:not(:first-child), +.btn-group-vertical > .btn-group:not(:first-child) > .btn { + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.btn-group-toggle > .btn, +.btn-group-toggle > .btn-group > .btn { + margin-bottom: 0; +} + +.btn-group-toggle > .btn input[type="radio"], +.btn-group-toggle > .btn input[type="checkbox"], +.btn-group-toggle > .btn-group > .btn input[type="radio"], +.btn-group-toggle > .btn-group > .btn input[type="checkbox"] { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; +} + +.input-group { + position: relative; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-box-align: stretch; + -ms-flex-align: stretch; + align-items: stretch; + width: 100%; +} + +.input-group .form-control, +.input-group .custom-select, +.input-group .custom-file { + position: relative; + -webkit-box-flex: 1; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + width: 1%; + margin-bottom: 0; +} + +.input-group .form-control:focus, +.input-group .custom-select:focus, +.input-group .custom-file:focus { + z-index: 3; +} + +.input-group .form-control + .form-control, +.input-group .custom-select + .form-control, +.input-group .custom-file + .form-control { + margin-left: -1px; +} + +.input-group .form-control:not(:last-child), +.input-group .custom-select:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.input-group .form-control:not(:first-child), +.input-group .custom-select:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.input-group .custom-file { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.input-group .custom-file:not(:last-child) .custom-file-control, +.input-group .custom-file:not(:last-child) .custom-file-control::before { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.input-group .custom-file:not(:first-child) .custom-file-control, +.input-group .custom-file:not(:first-child) .custom-file-control::before { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.input-group-prepend, +.input-group-append { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.input-group-prepend .btn, +.input-group-append .btn { + position: relative; + z-index: 2; +} + +.input-group-prepend .btn + .btn, +.input-group-prepend .btn + .input-group-text, +.input-group-prepend .input-group-text + .input-group-text, +.input-group-prepend .input-group-text + .btn, +.input-group-append .btn + .btn, +.input-group-append .btn + .input-group-text, +.input-group-append .input-group-text + .input-group-text, +.input-group-append .input-group-text + .btn { + margin-left: -1px; +} + +.input-group-prepend { + margin-right: -1px; +} + +.input-group-append { + margin-left: -1px; +} + +.input-group-text { + padding: 0.375rem 0.75rem; + margin-bottom: 0; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #495057; + text-align: center; + white-space: nowrap; + background-color: #e9ecef; + border: 1px solid #ced4da; + border-radius: 0.25rem; +} + +.input-group-text input[type="radio"], +.input-group-text input[type="checkbox"] { + margin-top: 0; +} + +.input-group > .input-group-prepend > .btn, +.input-group > .input-group-prepend > .input-group-text, +.input-group > .input-group-append:not(:last-child) > .btn, +.input-group > .input-group-append:not(:last-child) > .input-group-text, +.input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle), +.input-group > .input-group-append:last-child > .input-group-text:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.input-group > .input-group-append > .btn, +.input-group > .input-group-append > .input-group-text, +.input-group > .input-group-prepend:not(:first-child) > .btn, +.input-group > .input-group-prepend:not(:first-child) > .input-group-text, +.input-group > .input-group-prepend:first-child > .btn:not(:first-child), +.input-group > .input-group-prepend:first-child > .input-group-text:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.custom-control { + position: relative; + display: block; + min-height: 1.5rem; + padding-left: 1.5rem; +} + +.custom-control-inline { + display: -webkit-inline-box; + display: -ms-inline-flexbox; + display: inline-flex; + margin-right: 1rem; +} + +.custom-control-input { + position: absolute; + z-index: -1; + opacity: 0; +} + +.custom-control-input:checked ~ .custom-control-label::before { + color: #fff; + background-color: #2FA4E7; +} + +.custom-control-input:focus ~ .custom-control-label::before { + -webkit-box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(47, 164, 231, 0.25); + box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(47, 164, 231, 0.25); +} + +.custom-control-input:active ~ .custom-control-label::before { + color: #fff; + background-color: #cfeaf9; +} + +.custom-control-input:disabled ~ .custom-control-label { + color: #868e96; +} + +.custom-control-input:disabled ~ .custom-control-label::before { + background-color: #e9ecef; +} + +.custom-control-label { + margin-bottom: 0; +} + +.custom-control-label::before { + position: absolute; + top: 0.25rem; + left: 0; + display: block; + width: 1rem; + height: 1rem; + pointer-events: none; + content: ""; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-color: #dee2e6; +} + +.custom-control-label::after { + position: absolute; + top: 0.25rem; + left: 0; + display: block; + width: 1rem; + height: 1rem; + content: ""; + background-repeat: no-repeat; + background-position: center center; + background-size: 50% 50%; +} + +.custom-checkbox .custom-control-label::before { + border-radius: 0.25rem; +} + +.custom-checkbox .custom-control-input:checked ~ .custom-control-label::before { + background-color: #2FA4E7; +} + +.custom-checkbox .custom-control-input:checked ~ .custom-control-label::after { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E"); +} + +.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::before { + background-color: #2FA4E7; +} + +.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::after { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E"); +} + +.custom-radio .custom-control-label::before { + border-radius: 50%; +} + +.custom-radio .custom-control-input:checked ~ .custom-control-label::before { + background-color: #2FA4E7; +} + +.custom-radio .custom-control-input:checked ~ .custom-control-label::after { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E"); +} + +.custom-select { + display: inline-block; + width: 100%; + height: calc(2.25rem + 2px); + padding: 0.375rem 1.75rem 0.375rem 0.75rem; + line-height: 1.5; + color: #495057; + vertical-align: middle; + background: #fff url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") no-repeat right 0.75rem center; + background-size: 8px 10px; + border: 1px solid #ced4da; + border-radius: 0.25rem; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +.custom-select:focus { + border-color: #a1d6f4; + outline: 0; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075), 0 0 0 0.2rem rgba(47, 164, 231, 0.25); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075), 0 0 0 0.2rem rgba(47, 164, 231, 0.25); +} + +.custom-select:focus::-ms-value { + color: #495057; + background-color: #fff; +} + +.custom-select[multiple], .custom-select[size]:not([size="1"]) { + height: auto; + padding-right: 0.75rem; + background-image: none; +} + +.custom-select:disabled { + color: #868e96; + background-color: #e9ecef; +} + +.custom-select::-ms-expand { + opacity: 0; +} + +.custom-select-sm { + height: calc(1.8125rem + 2px); + padding-top: 0.375rem; + padding-bottom: 0.375rem; + font-size: 75%; +} + +.custom-select-lg { + height: calc(2.875rem + 2px); + padding-top: 0.375rem; + padding-bottom: 0.375rem; + font-size: 125%; +} + +.custom-file { + position: relative; + display: inline-block; + width: 100%; + height: calc(2.25rem + 2px); + margin-bottom: 0; +} + +.custom-file-input { + position: relative; + z-index: 2; + width: 100%; + height: calc(2.25rem + 2px); + margin: 0; + opacity: 0; +} + +.custom-file-input:focus ~ .custom-file-control { + border-color: #a1d6f4; + -webkit-box-shadow: 0 0 0 0.2rem rgba(47, 164, 231, 0.25); + box-shadow: 0 0 0 0.2rem rgba(47, 164, 231, 0.25); +} + +.custom-file-input:focus ~ .custom-file-control::before { + border-color: #a1d6f4; +} + +.custom-file-input:lang(en) ~ .custom-file-label::after { + content: "Browse"; +} + +.custom-file-label { + position: absolute; + top: 0; + right: 0; + left: 0; + z-index: 1; + height: calc(2.25rem + 2px); + padding: 0.375rem 0.75rem; + line-height: 1.5; + color: #495057; + background-color: #fff; + border: 1px solid #ced4da; + border-radius: 0.25rem; +} + +.custom-file-label::after { + position: absolute; + top: 0; + right: 0; + bottom: 0; + z-index: 3; + display: block; + height: calc(calc(2.25rem + 2px) - 1px * 2); + padding: 0.375rem 0.75rem; + line-height: 1.5; + color: #495057; + content: "Browse"; + background-color: #e9ecef; + border-left: 1px solid #ced4da; + border-radius: 0 0.25rem 0.25rem 0; +} + +.nav { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + padding-left: 0; + margin-bottom: 0; + list-style: none; +} + +.nav-link { + display: block; + padding: 0.5rem 1rem; +} + +.nav-link:focus, .nav-link:hover { + text-decoration: none; +} + +.nav-link.disabled { + color: #868e96; +} + +.nav-tabs { + border-bottom: 1px solid #dee2e6; +} + +.nav-tabs .nav-item { + margin-bottom: -1px; +} + +.nav-tabs .nav-link { + border: 1px solid transparent; + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; +} + +.nav-tabs .nav-link:focus, .nav-tabs .nav-link:hover { + border-color: #e9ecef #e9ecef #dee2e6; +} + +.nav-tabs .nav-link.disabled { + color: #868e96; + background-color: transparent; + border-color: transparent; +} + +.nav-tabs .nav-link.active, +.nav-tabs .nav-item.show .nav-link { + color: #495057; + background-color: #fff; + border-color: #dee2e6 #dee2e6 #fff; +} + +.nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.nav-pills .nav-link { + border-radius: 0.25rem; +} + +.nav-pills .nav-link.active, +.nav-pills .show > .nav-link { + color: #fff; + background-color: #2FA4E7; +} + +.nav-fill .nav-item { + -webkit-box-flex: 1; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + text-align: center; +} + +.nav-justified .nav-item { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + text-align: center; +} + +.tab-content > .tab-pane { + display: none; +} + +.tab-content > .active { + display: block; +} + +.navbar { + position: relative; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; + padding: 0.5rem 1rem; +} + +.navbar > .container, +.navbar > .container-fluid { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; +} + +.navbar-brand { + display: inline-block; + padding-top: 0.3125rem; + padding-bottom: 0.3125rem; + margin-right: 1rem; + font-size: 1.25rem; + line-height: inherit; + white-space: nowrap; +} + +.navbar-brand:focus, .navbar-brand:hover { + text-decoration: none; +} + +.navbar-nav { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + padding-left: 0; + margin-bottom: 0; + list-style: none; +} + +.navbar-nav .nav-link { + padding-right: 0; + padding-left: 0; +} + +.navbar-nav .dropdown-menu { + position: static; + float: none; +} + +.navbar-text { + display: inline-block; + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.navbar-collapse { + -ms-flex-preferred-size: 100%; + flex-basis: 100%; + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} + +.navbar-toggler { + padding: 0.25rem 0.75rem; + font-size: 1.25rem; + line-height: 1; + background-color: transparent; + border: 1px solid transparent; + border-radius: 0.25rem; +} + +.navbar-toggler:focus, .navbar-toggler:hover { + text-decoration: none; +} + +.navbar-toggler:not([disabled]):not(.disabled) { + cursor: pointer; +} + +.navbar-toggler-icon { + display: inline-block; + width: 1.5em; + height: 1.5em; + vertical-align: middle; + content: ""; + background: no-repeat center center; + background-size: 100% 100%; +} + +@media (max-width: 575.99px) { + .navbar-expand-sm > .container, + .navbar-expand-sm > .container-fluid { + padding-right: 0; + padding-left: 0; + } +} + +@media (min-width: 576px) { + .navbar-expand-sm { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + } + .navbar-expand-sm .navbar-nav { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + } + .navbar-expand-sm .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-sm .navbar-nav .dropdown-menu-right { + right: 0; + left: auto; + } + .navbar-expand-sm .navbar-nav .nav-link { + padding-right: .5rem; + padding-left: .5rem; + } + .navbar-expand-sm > .container, + .navbar-expand-sm > .container-fluid { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + } + .navbar-expand-sm .navbar-collapse { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; + } + .navbar-expand-sm .navbar-toggler { + display: none; + } + .navbar-expand-sm .dropup .dropdown-menu { + top: auto; + bottom: 100%; + } +} + +@media (max-width: 767.99px) { + .navbar-expand-md > .container, + .navbar-expand-md > .container-fluid { + padding-right: 0; + padding-left: 0; + } +} + +@media (min-width: 768px) { + .navbar-expand-md { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + } + .navbar-expand-md .navbar-nav { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + } + .navbar-expand-md .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-md .navbar-nav .dropdown-menu-right { + right: 0; + left: auto; + } + .navbar-expand-md .navbar-nav .nav-link { + padding-right: .5rem; + padding-left: .5rem; + } + .navbar-expand-md > .container, + .navbar-expand-md > .container-fluid { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + } + .navbar-expand-md .navbar-collapse { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; + } + .navbar-expand-md .navbar-toggler { + display: none; + } + .navbar-expand-md .dropup .dropdown-menu { + top: auto; + bottom: 100%; + } +} + +@media (max-width: 991.99px) { + .navbar-expand-lg > .container, + .navbar-expand-lg > .container-fluid { + padding-right: 0; + padding-left: 0; + } +} + +@media (min-width: 992px) { + .navbar-expand-lg { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + } + .navbar-expand-lg .navbar-nav { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + } + .navbar-expand-lg .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-lg .navbar-nav .dropdown-menu-right { + right: 0; + left: auto; + } + .navbar-expand-lg .navbar-nav .nav-link { + padding-right: .5rem; + padding-left: .5rem; + } + .navbar-expand-lg > .container, + .navbar-expand-lg > .container-fluid { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + } + .navbar-expand-lg .navbar-collapse { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; + } + .navbar-expand-lg .navbar-toggler { + display: none; + } + .navbar-expand-lg .dropup .dropdown-menu { + top: auto; + bottom: 100%; + } +} + +@media (max-width: 1199.99px) { + .navbar-expand-xl > .container, + .navbar-expand-xl > .container-fluid { + padding-right: 0; + padding-left: 0; + } +} + +@media (min-width: 1200px) { + .navbar-expand-xl { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + } + .navbar-expand-xl .navbar-nav { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + } + .navbar-expand-xl .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-xl .navbar-nav .dropdown-menu-right { + right: 0; + left: auto; + } + .navbar-expand-xl .navbar-nav .nav-link { + padding-right: .5rem; + padding-left: .5rem; + } + .navbar-expand-xl > .container, + .navbar-expand-xl > .container-fluid { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + } + .navbar-expand-xl .navbar-collapse { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; + } + .navbar-expand-xl .navbar-toggler { + display: none; + } + .navbar-expand-xl .dropup .dropdown-menu { + top: auto; + bottom: 100%; + } +} + +.navbar-expand { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.navbar-expand > .container, +.navbar-expand > .container-fluid { + padding-right: 0; + padding-left: 0; +} + +.navbar-expand .navbar-nav { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; +} + +.navbar-expand .navbar-nav .dropdown-menu { + position: absolute; +} + +.navbar-expand .navbar-nav .dropdown-menu-right { + right: 0; + left: auto; +} + +.navbar-expand .navbar-nav .nav-link { + padding-right: .5rem; + padding-left: .5rem; +} + +.navbar-expand > .container, +.navbar-expand > .container-fluid { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; +} + +.navbar-expand .navbar-collapse { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; +} + +.navbar-expand .navbar-toggler { + display: none; +} + +.navbar-expand .dropup .dropdown-menu { + top: auto; + bottom: 100%; +} + +.navbar-light .navbar-brand { + color: rgba(0, 0, 0, 0.9); +} + +.navbar-light .navbar-brand:focus, .navbar-light .navbar-brand:hover { + color: rgba(0, 0, 0, 0.9); +} + +.navbar-light .navbar-nav .nav-link { + color: rgba(0, 0, 0, 0.5); +} + +.navbar-light .navbar-nav .nav-link:focus, .navbar-light .navbar-nav .nav-link:hover { + color: rgba(0, 0, 0, 0.7); +} + +.navbar-light .navbar-nav .nav-link.disabled { + color: rgba(0, 0, 0, 0.3); +} + +.navbar-light .navbar-nav .show > .nav-link, +.navbar-light .navbar-nav .active > .nav-link, +.navbar-light .navbar-nav .nav-link.show, +.navbar-light .navbar-nav .nav-link.active { + color: rgba(0, 0, 0, 0.9); +} + +.navbar-light .navbar-toggler { + color: rgba(0, 0, 0, 0.5); + border-color: rgba(0, 0, 0, 0.1); +} + +.navbar-light .navbar-toggler-icon { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); +} + +.navbar-light .navbar-text { + color: rgba(0, 0, 0, 0.5); +} + +.navbar-light .navbar-text a { + color: rgba(0, 0, 0, 0.9); +} + +.navbar-light .navbar-text a:focus, .navbar-light .navbar-text a:hover { + color: rgba(0, 0, 0, 0.9); +} + +.navbar-dark .navbar-brand { + color: #fff; +} + +.navbar-dark .navbar-brand:focus, .navbar-dark .navbar-brand:hover { + color: #fff; +} + +.navbar-dark .navbar-nav .nav-link { + color: rgba(255, 255, 255, 0.8); +} + +.navbar-dark .navbar-nav .nav-link:focus, .navbar-dark .navbar-nav .nav-link:hover { + color: #fff; +} + +.navbar-dark .navbar-nav .nav-link.disabled { + color: rgba(255, 255, 255, 0.25); +} + +.navbar-dark .navbar-nav .show > .nav-link, +.navbar-dark .navbar-nav .active > .nav-link, +.navbar-dark .navbar-nav .nav-link.show, +.navbar-dark .navbar-nav .nav-link.active { + color: #fff; +} + +.navbar-dark .navbar-toggler { + color: rgba(255, 255, 255, 0.8); + border-color: rgba(255, 255, 255, 0.1); +} + +.navbar-dark .navbar-toggler-icon { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.8)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E"); +} + +.navbar-dark .navbar-text { + color: rgba(255, 255, 255, 0.8); +} + +.navbar-dark .navbar-text a { + color: #fff; +} + +.navbar-dark .navbar-text a:focus, .navbar-dark .navbar-text a:hover { + color: #fff; +} + +.card { + position: relative; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + min-width: 0; + word-wrap: break-word; + background-color: #fff; + background-clip: border-box; + border: 1px solid rgba(0, 0, 0, 0.125); + border-radius: 0.25rem; +} + +.card > hr { + margin-right: 0; + margin-left: 0; +} + +.card > .list-group:first-child .list-group-item:first-child { + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; +} + +.card > .list-group:last-child .list-group-item:last-child { + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; +} + +.card-body { + -webkit-box-flex: 1; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + padding: 1.25rem; +} + +.card-title { + margin-bottom: 0.75rem; +} + +.card-subtitle { + margin-top: -0.375rem; + margin-bottom: 0; +} + +.card-text:last-child { + margin-bottom: 0; +} + +.card-link:hover { + text-decoration: none; +} + +.card-link + .card-link { + margin-left: 1.25rem; +} + +.card-header { + padding: 0.75rem 1.25rem; + margin-bottom: 0; + background-color: rgba(0, 0, 0, 0.03); + border-bottom: 1px solid rgba(0, 0, 0, 0.125); +} + +.card-header:first-child { + border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0; +} + +.card-header + .list-group .list-group-item:first-child { + border-top: 0; +} + +.card-footer { + padding: 0.75rem 1.25rem; + background-color: rgba(0, 0, 0, 0.03); + border-top: 1px solid rgba(0, 0, 0, 0.125); +} + +.card-footer:last-child { + border-radius: 0 0 calc(0.25rem - 1px) calc(0.25rem - 1px); +} + +.card-header-tabs { + margin-right: -0.625rem; + margin-bottom: -0.75rem; + margin-left: -0.625rem; + border-bottom: 0; +} + +.card-header-pills { + margin-right: -0.625rem; + margin-left: -0.625rem; +} + +.card-img-overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: 1.25rem; +} + +.card-img { + width: 100%; + border-radius: calc(0.25rem - 1px); +} + +.card-img-top { + width: 100%; + border-top-left-radius: calc(0.25rem - 1px); + border-top-right-radius: calc(0.25rem - 1px); +} + +.card-img-bottom { + width: 100%; + border-bottom-right-radius: calc(0.25rem - 1px); + border-bottom-left-radius: calc(0.25rem - 1px); +} + +.card-deck { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; +} + +.card-deck .card { + margin-bottom: 15px; +} + +@media (min-width: 576px) { + .card-deck { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row wrap; + flex-flow: row wrap; + margin-right: -15px; + margin-left: -15px; + } + .card-deck .card { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 1; + -ms-flex: 1 0 0%; + flex: 1 0 0%; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + margin-right: 15px; + margin-bottom: 0; + margin-left: 15px; + } +} + +.card-group { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; +} + +.card-group > .card { + margin-bottom: 15px; +} + +@media (min-width: 576px) { + .card-group { + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-flow: row wrap; + flex-flow: row wrap; + } + .card-group > .card { + -webkit-box-flex: 1; + -ms-flex: 1 0 0%; + flex: 1 0 0%; + margin-bottom: 0; + } + .card-group > .card + .card { + margin-left: 0; + border-left: 0; + } + .card-group > .card:first-child { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + .card-group > .card:first-child .card-img-top, + .card-group > .card:first-child .card-header { + border-top-right-radius: 0; + } + .card-group > .card:first-child .card-img-bottom, + .card-group > .card:first-child .card-footer { + border-bottom-right-radius: 0; + } + .card-group > .card:last-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + .card-group > .card:last-child .card-img-top, + .card-group > .card:last-child .card-header { + border-top-left-radius: 0; + } + .card-group > .card:last-child .card-img-bottom, + .card-group > .card:last-child .card-footer { + border-bottom-left-radius: 0; + } + .card-group > .card:only-child { + border-radius: 0.25rem; + } + .card-group > .card:only-child .card-img-top, + .card-group > .card:only-child .card-header { + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; + } + .card-group > .card:only-child .card-img-bottom, + .card-group > .card:only-child .card-footer { + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; + } + .card-group > .card:not(:first-child):not(:last-child):not(:only-child) { + border-radius: 0; + } + .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-img-top, + .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-img-bottom, + .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-header, + .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-footer { + border-radius: 0; + } +} + +.card-columns .card { + margin-bottom: 0.75rem; +} + +@media (min-width: 576px) { + .card-columns { + -webkit-column-count: 3; + column-count: 3; + -webkit-column-gap: 1.25rem; + column-gap: 1.25rem; + } + .card-columns .card { + display: inline-block; + width: 100%; + } +} + +.breadcrumb { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + padding: 0.75rem 1rem; + margin-bottom: 1rem; + list-style: none; + background-color: #e9ecef; + border-radius: 0.25rem; +} + +.breadcrumb-item + .breadcrumb-item::before { + display: inline-block; + padding-right: 0.5rem; + padding-left: 0.5rem; + color: #868e96; + content: "/"; +} + +.breadcrumb-item + .breadcrumb-item:hover::before { + text-decoration: underline; +} + +.breadcrumb-item + .breadcrumb-item:hover::before { + text-decoration: none; +} + +.breadcrumb-item.active { + color: #868e96; +} + +.pagination { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + padding-left: 0; + list-style: none; + border-radius: 0.25rem; +} + +.page-link { + position: relative; + display: block; + padding: 0.5rem 0.75rem; + margin-left: -1px; + line-height: 1.25; + color: #2FA4E7; + background-color: #fff; + border: 1px solid #dee2e6; +} + +.page-link:focus, .page-link:hover { + color: #157ab5; + text-decoration: none; + background-color: #e9ecef; + border-color: #dee2e6; +} + +.page-link:not([disabled]):not(.disabled) { + cursor: pointer; +} + +.page-item:first-child .page-link { + margin-left: 0; + border-top-left-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; +} + +.page-item:last-child .page-link { + border-top-right-radius: 0.25rem; + border-bottom-right-radius: 0.25rem; +} + +.page-item.active .page-link { + z-index: 1; + color: #fff; + background-color: #2FA4E7; + border-color: #2FA4E7; +} + +.page-item.disabled .page-link { + color: #868e96; + pointer-events: none; + cursor: auto; + background-color: #fff; + border-color: #dee2e6; +} + +.pagination-lg .page-link { + padding: 0.75rem 1.5rem; + font-size: 1.25rem; + line-height: 1.5; +} + +.pagination-lg .page-item:first-child .page-link { + border-top-left-radius: 0.3rem; + border-bottom-left-radius: 0.3rem; +} + +.pagination-lg .page-item:last-child .page-link { + border-top-right-radius: 0.3rem; + border-bottom-right-radius: 0.3rem; +} + +.pagination-sm .page-link { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; +} + +.pagination-sm .page-item:first-child .page-link { + border-top-left-radius: 0.2rem; + border-bottom-left-radius: 0.2rem; +} + +.pagination-sm .page-item:last-child .page-link { + border-top-right-radius: 0.2rem; + border-bottom-right-radius: 0.2rem; +} + +.badge { + display: inline-block; + padding: 0.25em 0.4em; + font-size: 75%; + font-weight: 700; + line-height: 1; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: 0.25rem; +} + +.badge:empty { + display: none; +} + +.btn .badge { + position: relative; + top: -1px; +} + +.badge-pill { + padding-right: 0.6em; + padding-left: 0.6em; + border-radius: 10rem; +} + +.badge-primary { + color: #fff; + background-color: #2FA4E7; +} + +.badge-primary[href]:focus, .badge-primary[href]:hover { + color: #fff; + text-decoration: none; + background-color: #178acc; +} + +.badge-secondary { + color: #212529; + background-color: #e9ecef; +} + +.badge-secondary[href]:focus, .badge-secondary[href]:hover { + color: #212529; + text-decoration: none; + background-color: #cbd3da; +} + +.badge-success { + color: #fff; + background-color: #73A839; +} + +.badge-success[href]:focus, .badge-success[href]:hover { + color: #fff; + text-decoration: none; + background-color: #59822c; +} + +.badge-info { + color: #fff; + background-color: #033C73; +} + +.badge-info[href]:focus, .badge-info[href]:hover { + color: #fff; + text-decoration: none; + background-color: #022241; +} + +.badge-warning { + color: #fff; + background-color: #DD5600; +} + +.badge-warning[href]:focus, .badge-warning[href]:hover { + color: #fff; + text-decoration: none; + background-color: #aa4200; +} + +.badge-danger { + color: #fff; + background-color: #C71C22; +} + +.badge-danger[href]:focus, .badge-danger[href]:hover { + color: #fff; + text-decoration: none; + background-color: #9a161a; +} + +.badge-light { + color: #212529; + background-color: #f8f9fa; +} + +.badge-light[href]:focus, .badge-light[href]:hover { + color: #212529; + text-decoration: none; + background-color: #dae0e5; +} + +.badge-dark { + color: #fff; + background-color: #343a40; +} + +.badge-dark[href]:focus, .badge-dark[href]:hover { + color: #fff; + text-decoration: none; + background-color: #1d2124; +} + +.jumbotron { + padding: 2rem 1rem; + margin-bottom: 2rem; + background-color: #e9ecef; + border-radius: 0.3rem; +} + +@media (min-width: 576px) { + .jumbotron { + padding: 4rem 2rem; + } +} + +.jumbotron-fluid { + padding-right: 0; + padding-left: 0; + border-radius: 0; +} + +.alert { + position: relative; + padding: 0.75rem 1.25rem; + margin-bottom: 1rem; + border: 1px solid transparent; + border-radius: 0.25rem; +} + +.alert-heading { + color: inherit; +} + +.alert-link { + font-weight: 700; +} + +.alert-dismissible { + padding-right: 4rem; +} + +.alert-dismissible .close { + position: absolute; + top: 0; + right: 0; + padding: 0.75rem 1.25rem; + color: inherit; +} + +.alert-primary { + color: #185578; + background-color: #d5edfa; + border-color: #c5e6f8; +} + +.alert-primary hr { + border-top-color: #aedcf5; +} + +.alert-primary .alert-link { + color: #10374e; +} + +.alert-secondary { + color: #797b7c; + background-color: #fbfbfc; + border-color: #f9fafb; +} + +.alert-secondary hr { + border-top-color: #eaedf1; +} + +.alert-secondary .alert-link { + color: #606162; +} + +.alert-success { + color: #3c571e; + background-color: #e3eed7; + border-color: #d8e7c8; +} + +.alert-success hr { + border-top-color: #cbdfb6; +} + +.alert-success .alert-link { + color: #223111; +} + +.alert-info { + color: #021f3c; + background-color: #cdd8e3; + border-color: #b8c8d8; +} + +.alert-info hr { + border-top-color: #a8bbcf; +} + +.alert-info .alert-link { + color: #00060b; +} + +.alert-warning { + color: #732d00; + background-color: #f8ddcc; + border-color: #f5d0b8; +} + +.alert-warning hr { + border-top-color: #f2c1a2; +} + +.alert-warning .alert-link { + color: #401900; +} + +.alert-danger { + color: #670f12; + background-color: #f4d2d3; + border-color: #efbfc1; +} + +.alert-danger hr { + border-top-color: #eaabad; +} + +.alert-danger .alert-link { + color: #3a090a; +} + +.alert-light { + color: #818182; + background-color: #fefefe; + border-color: #fdfdfe; +} + +.alert-light hr { + border-top-color: #ececf6; +} + +.alert-light .alert-link { + color: #686868; +} + +.alert-dark { + color: #1b1e21; + background-color: #d6d8d9; + border-color: #c6c8ca; +} + +.alert-dark hr { + border-top-color: #b9bbbe; +} + +.alert-dark .alert-link { + color: #040505; +} + +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 1rem 0; + } + to { + background-position: 0 0; + } +} + +@keyframes progress-bar-stripes { + from { + background-position: 1rem 0; + } + to { + background-position: 0 0; + } +} + +.progress { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + height: 1rem; + overflow: hidden; + font-size: 0.75rem; + background-color: #e9ecef; + border-radius: 0.25rem; +} + +.progress-bar { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + color: #fff; + text-align: center; + background-color: #2FA4E7; + -webkit-transition: width 0.6s ease; + transition: width 0.6s ease; +} + +.progress-bar-striped { + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-size: 1rem 1rem; +} + +.progress-bar-animated { + -webkit-animation: progress-bar-stripes 1s linear infinite; + animation: progress-bar-stripes 1s linear infinite; +} + +.media { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: start; + -ms-flex-align: start; + align-items: flex-start; +} + +.media-body { + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; +} + +.list-group { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; +} + +.list-group-item-action { + width: 100%; + color: #495057; + text-align: inherit; +} + +.list-group-item-action:focus, .list-group-item-action:hover { + color: #495057; + text-decoration: none; + background-color: #f8f9fa; +} + +.list-group-item-action:active { + color: #868e96; + background-color: #e9ecef; +} + +.list-group-item { + position: relative; + display: block; + padding: 0.75rem 1.25rem; + margin-bottom: -1px; + background-color: #fff; + border: 1px solid rgba(0, 0, 0, 0.125); +} + +.list-group-item:first-child { + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; +} + +.list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; +} + +.list-group-item:focus, .list-group-item:hover { + z-index: 1; + text-decoration: none; +} + +.list-group-item.disabled, .list-group-item:disabled { + color: #868e96; + background-color: #fff; +} + +.list-group-item.active { + z-index: 2; + color: #fff; + background-color: #2FA4E7; + border-color: #2FA4E7; +} + +.list-group-flush .list-group-item { + border-right: 0; + border-left: 0; + border-radius: 0; +} + +.list-group-flush:first-child .list-group-item:first-child { + border-top: 0; +} + +.list-group-flush:last-child .list-group-item:last-child { + border-bottom: 0; +} + +.list-group-item-primary { + color: #185578; + background-color: #c5e6f8; +} + +a.list-group-item-primary, +button.list-group-item-primary { + color: #185578; +} + +a.list-group-item-primary:focus, a.list-group-item-primary:hover, +button.list-group-item-primary:focus, +button.list-group-item-primary:hover { + color: #185578; + background-color: #aedcf5; +} + +a.list-group-item-primary.active, +button.list-group-item-primary.active { + color: #fff; + background-color: #185578; + border-color: #185578; +} + +.list-group-item-secondary { + color: #797b7c; + background-color: #f9fafb; +} + +a.list-group-item-secondary, +button.list-group-item-secondary { + color: #797b7c; +} + +a.list-group-item-secondary:focus, a.list-group-item-secondary:hover, +button.list-group-item-secondary:focus, +button.list-group-item-secondary:hover { + color: #797b7c; + background-color: #eaedf1; +} + +a.list-group-item-secondary.active, +button.list-group-item-secondary.active { + color: #fff; + background-color: #797b7c; + border-color: #797b7c; +} + +.list-group-item-success { + color: #3c571e; + background-color: #d8e7c8; +} + +a.list-group-item-success, +button.list-group-item-success { + color: #3c571e; +} + +a.list-group-item-success:focus, a.list-group-item-success:hover, +button.list-group-item-success:focus, +button.list-group-item-success:hover { + color: #3c571e; + background-color: #cbdfb6; +} + +a.list-group-item-success.active, +button.list-group-item-success.active { + color: #fff; + background-color: #3c571e; + border-color: #3c571e; +} + +.list-group-item-info { + color: #021f3c; + background-color: #b8c8d8; +} + +a.list-group-item-info, +button.list-group-item-info { + color: #021f3c; +} + +a.list-group-item-info:focus, a.list-group-item-info:hover, +button.list-group-item-info:focus, +button.list-group-item-info:hover { + color: #021f3c; + background-color: #a8bbcf; +} + +a.list-group-item-info.active, +button.list-group-item-info.active { + color: #fff; + background-color: #021f3c; + border-color: #021f3c; +} + +.list-group-item-warning { + color: #732d00; + background-color: #f5d0b8; +} + +a.list-group-item-warning, +button.list-group-item-warning { + color: #732d00; +} + +a.list-group-item-warning:focus, a.list-group-item-warning:hover, +button.list-group-item-warning:focus, +button.list-group-item-warning:hover { + color: #732d00; + background-color: #f2c1a2; +} + +a.list-group-item-warning.active, +button.list-group-item-warning.active { + color: #fff; + background-color: #732d00; + border-color: #732d00; +} + +.list-group-item-danger { + color: #670f12; + background-color: #efbfc1; +} + +a.list-group-item-danger, +button.list-group-item-danger { + color: #670f12; +} + +a.list-group-item-danger:focus, a.list-group-item-danger:hover, +button.list-group-item-danger:focus, +button.list-group-item-danger:hover { + color: #670f12; + background-color: #eaabad; +} + +a.list-group-item-danger.active, +button.list-group-item-danger.active { + color: #fff; + background-color: #670f12; + border-color: #670f12; +} + +.list-group-item-light { + color: #818182; + background-color: #fdfdfe; +} + +a.list-group-item-light, +button.list-group-item-light { + color: #818182; +} + +a.list-group-item-light:focus, a.list-group-item-light:hover, +button.list-group-item-light:focus, +button.list-group-item-light:hover { + color: #818182; + background-color: #ececf6; +} + +a.list-group-item-light.active, +button.list-group-item-light.active { + color: #fff; + background-color: #818182; + border-color: #818182; +} + +.list-group-item-dark { + color: #1b1e21; + background-color: #c6c8ca; +} + +a.list-group-item-dark, +button.list-group-item-dark { + color: #1b1e21; +} + +a.list-group-item-dark:focus, a.list-group-item-dark:hover, +button.list-group-item-dark:focus, +button.list-group-item-dark:hover { + color: #1b1e21; + background-color: #b9bbbe; +} + +a.list-group-item-dark.active, +button.list-group-item-dark.active { + color: #fff; + background-color: #1b1e21; + border-color: #1b1e21; +} + +.close { + float: right; + font-size: 1.5rem; + font-weight: 700; + line-height: 1; + color: #000; + text-shadow: 0 1px 0 #fff; + opacity: .5; +} + +.close:focus, .close:hover { + color: #000; + text-decoration: none; + opacity: .75; +} + +.close:not([disabled]):not(.disabled) { + cursor: pointer; +} + +button.close { + padding: 0; + background-color: transparent; + border: 0; + -webkit-appearance: none; +} + +.modal-open { + overflow: hidden; +} + +.modal { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1050; + display: none; + overflow: hidden; + outline: 0; +} + +.modal-open .modal { + overflow-x: hidden; + overflow-y: auto; +} + +.modal-dialog { + position: relative; + width: auto; + margin: 0.5rem; + pointer-events: none; +} + +.modal.fade .modal-dialog { + -webkit-transition: -webkit-transform 0.3s ease-out; + transition: -webkit-transform 0.3s ease-out; + transition: transform 0.3s ease-out; + transition: transform 0.3s ease-out, -webkit-transform 0.3s ease-out; + -webkit-transform: translate(0, -25%); + transform: translate(0, -25%); +} + +.modal.show .modal-dialog { + -webkit-transform: translate(0, 0); + transform: translate(0, 0); +} + +.modal-dialog-centered { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + min-height: calc(100% - (0.5rem * 2)); +} + +.modal-content { + position: relative; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + width: 100%; + pointer-events: auto; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 0.3rem; + outline: 0; +} + +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + background-color: #000; +} + +.modal-backdrop.fade { + opacity: 0; +} + +.modal-backdrop.show { + opacity: 0.5; +} + +.modal-header { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: start; + -ms-flex-align: start; + align-items: flex-start; + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; + padding: 1rem; + border-bottom: 1px solid #e9ecef; + border-top-left-radius: 0.3rem; + border-top-right-radius: 0.3rem; +} + +.modal-header .close { + padding: 1rem; + margin: -1rem -1rem -1rem auto; +} + +.modal-title { + margin-bottom: 0; + line-height: 1.5; +} + +.modal-body { + position: relative; + -webkit-box-flex: 1; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + padding: 1rem; +} + +.modal-footer { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: end; + -ms-flex-pack: end; + justify-content: flex-end; + padding: 1rem; + border-top: 1px solid #e9ecef; +} + +.modal-footer > :not(:first-child) { + margin-left: .25rem; +} + +.modal-footer > :not(:last-child) { + margin-right: .25rem; +} + +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; +} + +@media (min-width: 576px) { + .modal-dialog { + max-width: 500px; + margin: 1.75rem auto; + } + .modal-dialog-centered { + min-height: calc(100% - (1.75rem * 2)); + } + .modal-sm { + max-width: 300px; + } +} + +@media (min-width: 992px) { + .modal-lg { + max-width: 800px; + } +} + +.tooltip { + position: absolute; + z-index: 1070; + display: block; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-style: normal; + font-weight: 400; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + white-space: normal; + line-break: auto; + font-size: 0.875rem; + word-wrap: break-word; + opacity: 0; +} + +.tooltip.show { + opacity: 0.9; +} + +.tooltip .arrow { + position: absolute; + display: block; + width: 0.8rem; + height: 0.4rem; +} + +.tooltip .arrow::before { + position: absolute; + content: ""; + border-color: transparent; + border-style: solid; +} + +.bs-tooltip-top, .bs-tooltip-auto[x-placement^="top"] { + padding: 0.4rem 0; +} + +.bs-tooltip-top .arrow, .bs-tooltip-auto[x-placement^="top"] .arrow { + bottom: 0; +} + +.bs-tooltip-top .arrow::before, .bs-tooltip-auto[x-placement^="top"] .arrow::before { + top: 0; + border-width: 0.4rem 0.4rem 0; + border-top-color: #000; +} + +.bs-tooltip-right, .bs-tooltip-auto[x-placement^="right"] { + padding: 0 0.4rem; +} + +.bs-tooltip-right .arrow, .bs-tooltip-auto[x-placement^="right"] .arrow { + left: 0; + width: 0.4rem; + height: 0.8rem; +} + +.bs-tooltip-right .arrow::before, .bs-tooltip-auto[x-placement^="right"] .arrow::before { + right: 0; + border-width: 0.4rem 0.4rem 0.4rem 0; + border-right-color: #000; +} + +.bs-tooltip-bottom, .bs-tooltip-auto[x-placement^="bottom"] { + padding: 0.4rem 0; +} + +.bs-tooltip-bottom .arrow, .bs-tooltip-auto[x-placement^="bottom"] .arrow { + top: 0; +} + +.bs-tooltip-bottom .arrow::before, .bs-tooltip-auto[x-placement^="bottom"] .arrow::before { + bottom: 0; + border-width: 0 0.4rem 0.4rem; + border-bottom-color: #000; +} + +.bs-tooltip-left, .bs-tooltip-auto[x-placement^="left"] { + padding: 0 0.4rem; +} + +.bs-tooltip-left .arrow, .bs-tooltip-auto[x-placement^="left"] .arrow { + right: 0; + width: 0.4rem; + height: 0.8rem; +} + +.bs-tooltip-left .arrow::before, .bs-tooltip-auto[x-placement^="left"] .arrow::before { + left: 0; + border-width: 0.4rem 0 0.4rem 0.4rem; + border-left-color: #000; +} + +.tooltip-inner { + max-width: 200px; + padding: 0.25rem 0.5rem; + color: #fff; + text-align: center; + background-color: #000; + border-radius: 0.25rem; +} + +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: block; + max-width: 276px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-style: normal; + font-weight: 400; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + white-space: normal; + line-break: auto; + font-size: 0.875rem; + word-wrap: break-word; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 0.3rem; +} + +.popover .arrow { + position: absolute; + display: block; + width: 1rem; + height: 0.5rem; + margin: 0 0.3rem; +} + +.popover .arrow::before, .popover .arrow::after { + position: absolute; + display: block; + content: ""; + border-color: transparent; + border-style: solid; +} + +.bs-popover-top, .bs-popover-auto[x-placement^="top"] { + margin-bottom: 0.5rem; +} + +.bs-popover-top .arrow, .bs-popover-auto[x-placement^="top"] .arrow { + bottom: calc((0.5rem + 1px) * -1); +} + +.bs-popover-top .arrow::before, .bs-popover-auto[x-placement^="top"] .arrow::before, +.bs-popover-top .arrow::after, .bs-popover-auto[x-placement^="top"] .arrow::after { + border-width: 0.5rem 0.5rem 0; +} + +.bs-popover-top .arrow::before, .bs-popover-auto[x-placement^="top"] .arrow::before { + bottom: 0; + border-top-color: rgba(0, 0, 0, 0.25); +} + +.bs-popover-top .arrow::after, .bs-popover-auto[x-placement^="top"] .arrow::after { + bottom: 1px; + border-top-color: #fff; +} + +.bs-popover-right, .bs-popover-auto[x-placement^="right"] { + margin-left: 0.5rem; +} + +.bs-popover-right .arrow, .bs-popover-auto[x-placement^="right"] .arrow { + left: calc((0.5rem + 1px) * -1); + width: 0.5rem; + height: 1rem; + margin: 0.3rem 0; +} + +.bs-popover-right .arrow::before, .bs-popover-auto[x-placement^="right"] .arrow::before, +.bs-popover-right .arrow::after, .bs-popover-auto[x-placement^="right"] .arrow::after { + border-width: 0.5rem 0.5rem 0.5rem 0; +} + +.bs-popover-right .arrow::before, .bs-popover-auto[x-placement^="right"] .arrow::before { + left: 0; + border-right-color: rgba(0, 0, 0, 0.25); +} + +.bs-popover-right .arrow::after, .bs-popover-auto[x-placement^="right"] .arrow::after { + left: 1px; + border-right-color: #fff; +} + +.bs-popover-bottom, .bs-popover-auto[x-placement^="bottom"] { + margin-top: 0.5rem; +} + +.bs-popover-bottom .arrow, .bs-popover-auto[x-placement^="bottom"] .arrow { + top: calc((0.5rem + 1px) * -1); +} + +.bs-popover-bottom .arrow::before, .bs-popover-auto[x-placement^="bottom"] .arrow::before, +.bs-popover-bottom .arrow::after, .bs-popover-auto[x-placement^="bottom"] .arrow::after { + border-width: 0 0.5rem 0.5rem 0.5rem; +} + +.bs-popover-bottom .arrow::before, .bs-popover-auto[x-placement^="bottom"] .arrow::before { + top: 0; + border-bottom-color: rgba(0, 0, 0, 0.25); +} + +.bs-popover-bottom .arrow::after, .bs-popover-auto[x-placement^="bottom"] .arrow::after { + top: 1px; + border-bottom-color: #fff; +} + +.bs-popover-bottom .popover-header::before, .bs-popover-auto[x-placement^="bottom"] .popover-header::before { + position: absolute; + top: 0; + left: 50%; + display: block; + width: 1rem; + margin-left: -0.5rem; + content: ""; + border-bottom: 1px solid #f7f7f7; +} + +.bs-popover-left, .bs-popover-auto[x-placement^="left"] { + margin-right: 0.5rem; +} + +.bs-popover-left .arrow, .bs-popover-auto[x-placement^="left"] .arrow { + right: calc((0.5rem + 1px) * -1); + width: 0.5rem; + height: 1rem; + margin: 0.3rem 0; +} + +.bs-popover-left .arrow::before, .bs-popover-auto[x-placement^="left"] .arrow::before, +.bs-popover-left .arrow::after, .bs-popover-auto[x-placement^="left"] .arrow::after { + border-width: 0.5rem 0 0.5rem 0.5rem; +} + +.bs-popover-left .arrow::before, .bs-popover-auto[x-placement^="left"] .arrow::before { + right: 0; + border-left-color: rgba(0, 0, 0, 0.25); +} + +.bs-popover-left .arrow::after, .bs-popover-auto[x-placement^="left"] .arrow::after { + right: 1px; + border-left-color: #fff; +} + +.popover-header { + padding: 0.5rem 0.75rem; + margin-bottom: 0; + font-size: 1rem; + color: #2FA4E7; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-top-left-radius: calc(0.3rem - 1px); + border-top-right-radius: calc(0.3rem - 1px); +} + +.popover-header:empty { + display: none; +} + +.popover-body { + padding: 0.5rem 0.75rem; + color: #868e96; +} + +.carousel { + position: relative; +} + +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; +} + +.carousel-item { + position: relative; + display: none; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + width: 100%; + -webkit-transition: -webkit-transform 0.6s ease; + transition: -webkit-transform 0.6s ease; + transition: transform 0.6s ease; + transition: transform 0.6s ease, -webkit-transform 0.6s ease; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + -webkit-perspective: 1000px; + perspective: 1000px; +} + +.carousel-item.active, +.carousel-item-next, +.carousel-item-prev { + display: block; +} + +.carousel-item-next, +.carousel-item-prev { + position: absolute; + top: 0; +} + +.carousel-item-next.carousel-item-left, +.carousel-item-prev.carousel-item-right { + -webkit-transform: translateX(0); + transform: translateX(0); +} + +@supports ((-webkit-transform-style: preserve-3d) or (transform-style: preserve-3d)) { + .carousel-item-next.carousel-item-left, + .carousel-item-prev.carousel-item-right { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.carousel-item-next, +.active.carousel-item-right { + -webkit-transform: translateX(100%); + transform: translateX(100%); +} + +@supports ((-webkit-transform-style: preserve-3d) or (transform-style: preserve-3d)) { + .carousel-item-next, + .active.carousel-item-right { + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } +} + +.carousel-item-prev, +.active.carousel-item-left { + -webkit-transform: translateX(-100%); + transform: translateX(-100%); +} + +@supports ((-webkit-transform-style: preserve-3d) or (transform-style: preserve-3d)) { + .carousel-item-prev, + .active.carousel-item-left { + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } +} + +.carousel-control-prev, +.carousel-control-next { + position: absolute; + top: 0; + bottom: 0; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + width: 15%; + color: #fff; + text-align: center; + opacity: 0.5; +} + +.carousel-control-prev:focus, .carousel-control-prev:hover, +.carousel-control-next:focus, +.carousel-control-next:hover { + color: #fff; + text-decoration: none; + outline: 0; + opacity: .9; +} + +.carousel-control-prev { + left: 0; +} + +.carousel-control-next { + right: 0; +} + +.carousel-control-prev-icon, +.carousel-control-next-icon { + display: inline-block; + width: 20px; + height: 20px; + background: transparent no-repeat center center; + background-size: 100% 100%; +} + +.carousel-control-prev-icon { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E"); +} + +.carousel-control-next-icon { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E"); +} + +.carousel-indicators { + position: absolute; + right: 0; + bottom: 10px; + left: 0; + z-index: 15; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + padding-left: 0; + margin-right: 15%; + margin-left: 15%; + list-style: none; +} + +.carousel-indicators li { + position: relative; + -webkit-box-flex: 0; + -ms-flex: 0 1 auto; + flex: 0 1 auto; + width: 30px; + height: 3px; + margin-right: 3px; + margin-left: 3px; + text-indent: -999px; + background-color: rgba(255, 255, 255, 0.5); +} + +.carousel-indicators li::before { + position: absolute; + top: -10px; + left: 0; + display: inline-block; + width: 100%; + height: 10px; + content: ""; +} + +.carousel-indicators li::after { + position: absolute; + bottom: -10px; + left: 0; + display: inline-block; + width: 100%; + height: 10px; + content: ""; +} + +.carousel-indicators .active { + background-color: #fff; +} + +.carousel-caption { + position: absolute; + right: 15%; + bottom: 20px; + left: 15%; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #fff; + text-align: center; +} + +.align-baseline { + vertical-align: baseline !important; +} + +.align-top { + vertical-align: top !important; +} + +.align-middle { + vertical-align: middle !important; +} + +.align-bottom { + vertical-align: bottom !important; +} + +.align-text-bottom { + vertical-align: text-bottom !important; +} + +.align-text-top { + vertical-align: text-top !important; +} + +.bg-primary { + background-color: #2FA4E7 !important; +} + +a.bg-primary:focus, a.bg-primary:hover, +button.bg-primary:focus, +button.bg-primary:hover { + background-color: #178acc !important; +} + +.bg-secondary { + background-color: #e9ecef !important; +} + +a.bg-secondary:focus, a.bg-secondary:hover, +button.bg-secondary:focus, +button.bg-secondary:hover { + background-color: #cbd3da !important; +} + +.bg-success { + background-color: #73A839 !important; +} + +a.bg-success:focus, a.bg-success:hover, +button.bg-success:focus, +button.bg-success:hover { + background-color: #59822c !important; +} + +.bg-info { + background-color: #033C73 !important; +} + +a.bg-info:focus, a.bg-info:hover, +button.bg-info:focus, +button.bg-info:hover { + background-color: #022241 !important; +} + +.bg-warning { + background-color: #DD5600 !important; +} + +a.bg-warning:focus, a.bg-warning:hover, +button.bg-warning:focus, +button.bg-warning:hover { + background-color: #aa4200 !important; +} + +.bg-danger { + background-color: #C71C22 !important; +} + +a.bg-danger:focus, a.bg-danger:hover, +button.bg-danger:focus, +button.bg-danger:hover { + background-color: #9a161a !important; +} + +.bg-light { + background-color: #f8f9fa !important; +} + +a.bg-light:focus, a.bg-light:hover, +button.bg-light:focus, +button.bg-light:hover { + background-color: #dae0e5 !important; +} + +.bg-dark { + background-color: #343a40 !important; +} + +a.bg-dark:focus, a.bg-dark:hover, +button.bg-dark:focus, +button.bg-dark:hover { + background-color: #1d2124 !important; +} + +.bg-white { + background-color: #fff !important; +} + +.bg-transparent { + background-color: transparent !important; +} + +.border { + border: 1px solid #e9ecef !important; +} + +.border-0 { + border: 0 !important; +} + +.border-top-0 { + border-top: 0 !important; +} + +.border-right-0 { + border-right: 0 !important; +} + +.border-bottom-0 { + border-bottom: 0 !important; +} + +.border-left-0 { + border-left: 0 !important; +} + +.border-primary { + border-color: #2FA4E7 !important; +} + +.border-secondary { + border-color: #e9ecef !important; +} + +.border-success { + border-color: #73A839 !important; +} + +.border-info { + border-color: #033C73 !important; +} + +.border-warning { + border-color: #DD5600 !important; +} + +.border-danger { + border-color: #C71C22 !important; +} + +.border-light { + border-color: #f8f9fa !important; +} + +.border-dark { + border-color: #343a40 !important; +} + +.border-white { + border-color: #fff !important; +} + +.rounded { + border-radius: 0.25rem !important; +} + +.rounded-top { + border-top-left-radius: 0.25rem !important; + border-top-right-radius: 0.25rem !important; +} + +.rounded-right { + border-top-right-radius: 0.25rem !important; + border-bottom-right-radius: 0.25rem !important; +} + +.rounded-bottom { + border-bottom-right-radius: 0.25rem !important; + border-bottom-left-radius: 0.25rem !important; +} + +.rounded-left { + border-top-left-radius: 0.25rem !important; + border-bottom-left-radius: 0.25rem !important; +} + +.rounded-circle { + border-radius: 50% !important; +} + +.rounded-0 { + border-radius: 0 !important; +} + +.clearfix::after { + display: block; + clear: both; + content: ""; +} + +.d-none { + display: none !important; +} + +.d-inline { + display: inline !important; +} + +.d-inline-block { + display: inline-block !important; +} + +.d-block { + display: block !important; +} + +.d-table { + display: table !important; +} + +.d-table-row { + display: table-row !important; +} + +.d-table-cell { + display: table-cell !important; +} + +.d-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; +} + +.d-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; +} + +@media (min-width: 576px) { + .d-sm-none { + display: none !important; + } + .d-sm-inline { + display: inline !important; + } + .d-sm-inline-block { + display: inline-block !important; + } + .d-sm-block { + display: block !important; + } + .d-sm-table { + display: table !important; + } + .d-sm-table-row { + display: table-row !important; + } + .d-sm-table-cell { + display: table-cell !important; + } + .d-sm-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + } + .d-sm-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} + +@media (min-width: 768px) { + .d-md-none { + display: none !important; + } + .d-md-inline { + display: inline !important; + } + .d-md-inline-block { + display: inline-block !important; + } + .d-md-block { + display: block !important; + } + .d-md-table { + display: table !important; + } + .d-md-table-row { + display: table-row !important; + } + .d-md-table-cell { + display: table-cell !important; + } + .d-md-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + } + .d-md-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} + +@media (min-width: 992px) { + .d-lg-none { + display: none !important; + } + .d-lg-inline { + display: inline !important; + } + .d-lg-inline-block { + display: inline-block !important; + } + .d-lg-block { + display: block !important; + } + .d-lg-table { + display: table !important; + } + .d-lg-table-row { + display: table-row !important; + } + .d-lg-table-cell { + display: table-cell !important; + } + .d-lg-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + } + .d-lg-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} + +@media (min-width: 1200px) { + .d-xl-none { + display: none !important; + } + .d-xl-inline { + display: inline !important; + } + .d-xl-inline-block { + display: inline-block !important; + } + .d-xl-block { + display: block !important; + } + .d-xl-table { + display: table !important; + } + .d-xl-table-row { + display: table-row !important; + } + .d-xl-table-cell { + display: table-cell !important; + } + .d-xl-flex { + display: -webkit-box !important; + display: -ms-flexbox !important; + display: flex !important; + } + .d-xl-inline-flex { + display: -webkit-inline-box !important; + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} + +.d-print-block { + display: none !important; +} + +@media print { + .d-print-block { + display: block !important; + } +} + +.d-print-inline { + display: none !important; +} + +@media print { + .d-print-inline { + display: inline !important; + } +} + +.d-print-inline-block { + display: none !important; +} + +@media print { + .d-print-inline-block { + display: inline-block !important; + } +} + +@media print { + .d-print-none { + display: none !important; + } +} + +.embed-responsive { + position: relative; + display: block; + width: 100%; + padding: 0; + overflow: hidden; +} + +.embed-responsive::before { + display: block; + content: ""; +} + +.embed-responsive .embed-responsive-item, +.embed-responsive iframe, +.embed-responsive embed, +.embed-responsive object, +.embed-responsive video { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; +} + +.embed-responsive-21by9::before { + padding-top: 42.8571428571%; +} + +.embed-responsive-16by9::before { + padding-top: 56.25%; +} + +.embed-responsive-4by3::before { + padding-top: 75%; +} + +.embed-responsive-1by1::before { + padding-top: 100%; +} + +.flex-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; +} + +.flex-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; +} + +.flex-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; +} + +.flex-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; +} + +.flex-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; +} + +.flex-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; +} + +.flex-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; +} + +.justify-content-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; +} + +.justify-content-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; +} + +.justify-content-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; +} + +.justify-content-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; +} + +.justify-content-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; +} + +.align-items-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; +} + +.align-items-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; +} + +.align-items-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; +} + +.align-items-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; +} + +.align-items-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; +} + +.align-content-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; +} + +.align-content-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; +} + +.align-content-center { + -ms-flex-line-pack: center !important; + align-content: center !important; +} + +.align-content-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; +} + +.align-content-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; +} + +.align-content-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; +} + +.align-self-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; +} + +.align-self-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; +} + +.align-self-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; +} + +.align-self-center { + -ms-flex-item-align: center !important; + align-self: center !important; +} + +.align-self-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; +} + +.align-self-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; +} + +@media (min-width: 576px) { + .flex-sm-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-sm-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-sm-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-sm-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-sm-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-sm-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-sm-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .justify-content-sm-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-sm-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-sm-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-sm-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-sm-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-sm-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-sm-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-sm-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-sm-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-sm-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-sm-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-sm-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-sm-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-sm-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-sm-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-sm-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-sm-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-sm-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-sm-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-sm-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-sm-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-sm-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} + +@media (min-width: 768px) { + .flex-md-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-md-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-md-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-md-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-md-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-md-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-md-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .justify-content-md-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-md-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-md-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-md-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-md-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-md-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-md-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-md-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-md-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-md-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-md-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-md-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-md-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-md-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-md-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-md-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-md-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-md-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-md-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-md-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-md-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-md-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} + +@media (min-width: 992px) { + .flex-lg-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-lg-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-lg-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-lg-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-lg-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-lg-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-lg-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .justify-content-lg-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-lg-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-lg-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-lg-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-lg-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-lg-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-lg-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-lg-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-lg-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-lg-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-lg-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-lg-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-lg-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-lg-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-lg-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-lg-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-lg-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-lg-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-lg-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-lg-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-lg-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-lg-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} + +@media (min-width: 1200px) { + .flex-xl-row { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-xl-column { + -webkit-box-orient: vertical !important; + -webkit-box-direction: normal !important; + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-xl-row-reverse { + -webkit-box-orient: horizontal !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-xl-column-reverse { + -webkit-box-orient: vertical !important; + -webkit-box-direction: reverse !important; + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-xl-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-xl-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-xl-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .justify-content-xl-start { + -webkit-box-pack: start !important; + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-xl-end { + -webkit-box-pack: end !important; + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-xl-center { + -webkit-box-pack: center !important; + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-xl-between { + -webkit-box-pack: justify !important; + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-xl-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-xl-start { + -webkit-box-align: start !important; + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-xl-end { + -webkit-box-align: end !important; + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-xl-center { + -webkit-box-align: center !important; + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-xl-baseline { + -webkit-box-align: baseline !important; + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-xl-stretch { + -webkit-box-align: stretch !important; + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-xl-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-xl-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-xl-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-xl-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-xl-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-xl-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-xl-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-xl-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-xl-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-xl-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-xl-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-xl-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} + +.float-left { + float: left !important; +} + +.float-right { + float: right !important; +} + +.float-none { + float: none !important; +} + +@media (min-width: 576px) { + .float-sm-left { + float: left !important; + } + .float-sm-right { + float: right !important; + } + .float-sm-none { + float: none !important; + } +} + +@media (min-width: 768px) { + .float-md-left { + float: left !important; + } + .float-md-right { + float: right !important; + } + .float-md-none { + float: none !important; + } +} + +@media (min-width: 992px) { + .float-lg-left { + float: left !important; + } + .float-lg-right { + float: right !important; + } + .float-lg-none { + float: none !important; + } +} + +@media (min-width: 1200px) { + .float-xl-left { + float: left !important; + } + .float-xl-right { + float: right !important; + } + .float-xl-none { + float: none !important; + } +} + +.position-static { + position: static !important; +} + +.position-relative { + position: relative !important; +} + +.position-absolute { + position: absolute !important; +} + +.position-fixed { + position: fixed !important; +} + +.position-sticky { + position: -webkit-sticky !important; + position: sticky !important; +} + +.fixed-top { + position: fixed; + top: 0; + right: 0; + left: 0; + z-index: 1030; +} + +.fixed-bottom { + position: fixed; + right: 0; + bottom: 0; + left: 0; + z-index: 1030; +} + +@supports ((position: -webkit-sticky) or (position: sticky)) { + .sticky-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; + } +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + -webkit-clip-path: inset(50%); + clip-path: inset(50%); + border: 0; +} + +.sr-only-focusable:active, .sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + overflow: visible; + clip: auto; + white-space: normal; + -webkit-clip-path: none; + clip-path: none; +} + +.w-25 { + width: 25% !important; +} + +.w-50 { + width: 50% !important; +} + +.w-75 { + width: 75% !important; +} + +.w-100 { + width: 100% !important; +} + +.h-25 { + height: 25% !important; +} + +.h-50 { + height: 50% !important; +} + +.h-75 { + height: 75% !important; +} + +.h-100 { + height: 100% !important; +} + +.mw-100 { + max-width: 100% !important; +} + +.mh-100 { + max-height: 100% !important; +} + +.m-0 { + margin: 0 !important; +} + +.mt-0, +.my-0 { + margin-top: 0 !important; +} + +.mr-0, +.mx-0 { + margin-right: 0 !important; +} + +.mb-0, +.my-0 { + margin-bottom: 0 !important; +} + +.ml-0, +.mx-0 { + margin-left: 0 !important; +} + +.m-1 { + margin: 0.25rem !important; +} + +.mt-1, +.my-1 { + margin-top: 0.25rem !important; +} + +.mr-1, +.mx-1 { + margin-right: 0.25rem !important; +} + +.mb-1, +.my-1 { + margin-bottom: 0.25rem !important; +} + +.ml-1, +.mx-1 { + margin-left: 0.25rem !important; +} + +.m-2 { + margin: 0.5rem !important; +} + +.mt-2, +.my-2 { + margin-top: 0.5rem !important; +} + +.mr-2, +.mx-2 { + margin-right: 0.5rem !important; +} + +.mb-2, +.my-2 { + margin-bottom: 0.5rem !important; +} + +.ml-2, +.mx-2 { + margin-left: 0.5rem !important; +} + +.m-3 { + margin: 1rem !important; +} + +.mt-3, +.my-3 { + margin-top: 1rem !important; +} + +.mr-3, +.mx-3 { + margin-right: 1rem !important; +} + +.mb-3, +.my-3 { + margin-bottom: 1rem !important; +} + +.ml-3, +.mx-3 { + margin-left: 1rem !important; +} + +.m-4 { + margin: 1.5rem !important; +} + +.mt-4, +.my-4 { + margin-top: 1.5rem !important; +} + +.mr-4, +.mx-4 { + margin-right: 1.5rem !important; +} + +.mb-4, +.my-4 { + margin-bottom: 1.5rem !important; +} + +.ml-4, +.mx-4 { + margin-left: 1.5rem !important; +} + +.m-5 { + margin: 3rem !important; +} + +.mt-5, +.my-5 { + margin-top: 3rem !important; +} + +.mr-5, +.mx-5 { + margin-right: 3rem !important; +} + +.mb-5, +.my-5 { + margin-bottom: 3rem !important; +} + +.ml-5, +.mx-5 { + margin-left: 3rem !important; +} + +.p-0 { + padding: 0 !important; +} + +.pt-0, +.py-0 { + padding-top: 0 !important; +} + +.pr-0, +.px-0 { + padding-right: 0 !important; +} + +.pb-0, +.py-0 { + padding-bottom: 0 !important; +} + +.pl-0, +.px-0 { + padding-left: 0 !important; +} + +.p-1 { + padding: 0.25rem !important; +} + +.pt-1, +.py-1 { + padding-top: 0.25rem !important; +} + +.pr-1, +.px-1 { + padding-right: 0.25rem !important; +} + +.pb-1, +.py-1 { + padding-bottom: 0.25rem !important; +} + +.pl-1, +.px-1 { + padding-left: 0.25rem !important; +} + +.p-2 { + padding: 0.5rem !important; +} + +.pt-2, +.py-2 { + padding-top: 0.5rem !important; +} + +.pr-2, +.px-2 { + padding-right: 0.5rem !important; +} + +.pb-2, +.py-2 { + padding-bottom: 0.5rem !important; +} + +.pl-2, +.px-2 { + padding-left: 0.5rem !important; +} + +.p-3 { + padding: 1rem !important; +} + +.pt-3, +.py-3 { + padding-top: 1rem !important; +} + +.pr-3, +.px-3 { + padding-right: 1rem !important; +} + +.pb-3, +.py-3 { + padding-bottom: 1rem !important; +} + +.pl-3, +.px-3 { + padding-left: 1rem !important; +} + +.p-4 { + padding: 1.5rem !important; +} + +.pt-4, +.py-4 { + padding-top: 1.5rem !important; +} + +.pr-4, +.px-4 { + padding-right: 1.5rem !important; +} + +.pb-4, +.py-4 { + padding-bottom: 1.5rem !important; +} + +.pl-4, +.px-4 { + padding-left: 1.5rem !important; +} + +.p-5 { + padding: 3rem !important; +} + +.pt-5, +.py-5 { + padding-top: 3rem !important; +} + +.pr-5, +.px-5 { + padding-right: 3rem !important; +} + +.pb-5, +.py-5 { + padding-bottom: 3rem !important; +} + +.pl-5, +.px-5 { + padding-left: 3rem !important; +} + +.m-auto { + margin: auto !important; +} + +.mt-auto, +.my-auto { + margin-top: auto !important; +} + +.mr-auto, +.mx-auto { + margin-right: auto !important; +} + +.mb-auto, +.my-auto { + margin-bottom: auto !important; +} + +.ml-auto, +.mx-auto { + margin-left: auto !important; +} + +@media (min-width: 576px) { + .m-sm-0 { + margin: 0 !important; + } + .mt-sm-0, + .my-sm-0 { + margin-top: 0 !important; + } + .mr-sm-0, + .mx-sm-0 { + margin-right: 0 !important; + } + .mb-sm-0, + .my-sm-0 { + margin-bottom: 0 !important; + } + .ml-sm-0, + .mx-sm-0 { + margin-left: 0 !important; + } + .m-sm-1 { + margin: 0.25rem !important; + } + .mt-sm-1, + .my-sm-1 { + margin-top: 0.25rem !important; + } + .mr-sm-1, + .mx-sm-1 { + margin-right: 0.25rem !important; + } + .mb-sm-1, + .my-sm-1 { + margin-bottom: 0.25rem !important; + } + .ml-sm-1, + .mx-sm-1 { + margin-left: 0.25rem !important; + } + .m-sm-2 { + margin: 0.5rem !important; + } + .mt-sm-2, + .my-sm-2 { + margin-top: 0.5rem !important; + } + .mr-sm-2, + .mx-sm-2 { + margin-right: 0.5rem !important; + } + .mb-sm-2, + .my-sm-2 { + margin-bottom: 0.5rem !important; + } + .ml-sm-2, + .mx-sm-2 { + margin-left: 0.5rem !important; + } + .m-sm-3 { + margin: 1rem !important; + } + .mt-sm-3, + .my-sm-3 { + margin-top: 1rem !important; + } + .mr-sm-3, + .mx-sm-3 { + margin-right: 1rem !important; + } + .mb-sm-3, + .my-sm-3 { + margin-bottom: 1rem !important; + } + .ml-sm-3, + .mx-sm-3 { + margin-left: 1rem !important; + } + .m-sm-4 { + margin: 1.5rem !important; + } + .mt-sm-4, + .my-sm-4 { + margin-top: 1.5rem !important; + } + .mr-sm-4, + .mx-sm-4 { + margin-right: 1.5rem !important; + } + .mb-sm-4, + .my-sm-4 { + margin-bottom: 1.5rem !important; + } + .ml-sm-4, + .mx-sm-4 { + margin-left: 1.5rem !important; + } + .m-sm-5 { + margin: 3rem !important; + } + .mt-sm-5, + .my-sm-5 { + margin-top: 3rem !important; + } + .mr-sm-5, + .mx-sm-5 { + margin-right: 3rem !important; + } + .mb-sm-5, + .my-sm-5 { + margin-bottom: 3rem !important; + } + .ml-sm-5, + .mx-sm-5 { + margin-left: 3rem !important; + } + .p-sm-0 { + padding: 0 !important; + } + .pt-sm-0, + .py-sm-0 { + padding-top: 0 !important; + } + .pr-sm-0, + .px-sm-0 { + padding-right: 0 !important; + } + .pb-sm-0, + .py-sm-0 { + padding-bottom: 0 !important; + } + .pl-sm-0, + .px-sm-0 { + padding-left: 0 !important; + } + .p-sm-1 { + padding: 0.25rem !important; + } + .pt-sm-1, + .py-sm-1 { + padding-top: 0.25rem !important; + } + .pr-sm-1, + .px-sm-1 { + padding-right: 0.25rem !important; + } + .pb-sm-1, + .py-sm-1 { + padding-bottom: 0.25rem !important; + } + .pl-sm-1, + .px-sm-1 { + padding-left: 0.25rem !important; + } + .p-sm-2 { + padding: 0.5rem !important; + } + .pt-sm-2, + .py-sm-2 { + padding-top: 0.5rem !important; + } + .pr-sm-2, + .px-sm-2 { + padding-right: 0.5rem !important; + } + .pb-sm-2, + .py-sm-2 { + padding-bottom: 0.5rem !important; + } + .pl-sm-2, + .px-sm-2 { + padding-left: 0.5rem !important; + } + .p-sm-3 { + padding: 1rem !important; + } + .pt-sm-3, + .py-sm-3 { + padding-top: 1rem !important; + } + .pr-sm-3, + .px-sm-3 { + padding-right: 1rem !important; + } + .pb-sm-3, + .py-sm-3 { + padding-bottom: 1rem !important; + } + .pl-sm-3, + .px-sm-3 { + padding-left: 1rem !important; + } + .p-sm-4 { + padding: 1.5rem !important; + } + .pt-sm-4, + .py-sm-4 { + padding-top: 1.5rem !important; + } + .pr-sm-4, + .px-sm-4 { + padding-right: 1.5rem !important; + } + .pb-sm-4, + .py-sm-4 { + padding-bottom: 1.5rem !important; + } + .pl-sm-4, + .px-sm-4 { + padding-left: 1.5rem !important; + } + .p-sm-5 { + padding: 3rem !important; + } + .pt-sm-5, + .py-sm-5 { + padding-top: 3rem !important; + } + .pr-sm-5, + .px-sm-5 { + padding-right: 3rem !important; + } + .pb-sm-5, + .py-sm-5 { + padding-bottom: 3rem !important; + } + .pl-sm-5, + .px-sm-5 { + padding-left: 3rem !important; + } + .m-sm-auto { + margin: auto !important; + } + .mt-sm-auto, + .my-sm-auto { + margin-top: auto !important; + } + .mr-sm-auto, + .mx-sm-auto { + margin-right: auto !important; + } + .mb-sm-auto, + .my-sm-auto { + margin-bottom: auto !important; + } + .ml-sm-auto, + .mx-sm-auto { + margin-left: auto !important; + } +} + +@media (min-width: 768px) { + .m-md-0 { + margin: 0 !important; + } + .mt-md-0, + .my-md-0 { + margin-top: 0 !important; + } + .mr-md-0, + .mx-md-0 { + margin-right: 0 !important; + } + .mb-md-0, + .my-md-0 { + margin-bottom: 0 !important; + } + .ml-md-0, + .mx-md-0 { + margin-left: 0 !important; + } + .m-md-1 { + margin: 0.25rem !important; + } + .mt-md-1, + .my-md-1 { + margin-top: 0.25rem !important; + } + .mr-md-1, + .mx-md-1 { + margin-right: 0.25rem !important; + } + .mb-md-1, + .my-md-1 { + margin-bottom: 0.25rem !important; + } + .ml-md-1, + .mx-md-1 { + margin-left: 0.25rem !important; + } + .m-md-2 { + margin: 0.5rem !important; + } + .mt-md-2, + .my-md-2 { + margin-top: 0.5rem !important; + } + .mr-md-2, + .mx-md-2 { + margin-right: 0.5rem !important; + } + .mb-md-2, + .my-md-2 { + margin-bottom: 0.5rem !important; + } + .ml-md-2, + .mx-md-2 { + margin-left: 0.5rem !important; + } + .m-md-3 { + margin: 1rem !important; + } + .mt-md-3, + .my-md-3 { + margin-top: 1rem !important; + } + .mr-md-3, + .mx-md-3 { + margin-right: 1rem !important; + } + .mb-md-3, + .my-md-3 { + margin-bottom: 1rem !important; + } + .ml-md-3, + .mx-md-3 { + margin-left: 1rem !important; + } + .m-md-4 { + margin: 1.5rem !important; + } + .mt-md-4, + .my-md-4 { + margin-top: 1.5rem !important; + } + .mr-md-4, + .mx-md-4 { + margin-right: 1.5rem !important; + } + .mb-md-4, + .my-md-4 { + margin-bottom: 1.5rem !important; + } + .ml-md-4, + .mx-md-4 { + margin-left: 1.5rem !important; + } + .m-md-5 { + margin: 3rem !important; + } + .mt-md-5, + .my-md-5 { + margin-top: 3rem !important; + } + .mr-md-5, + .mx-md-5 { + margin-right: 3rem !important; + } + .mb-md-5, + .my-md-5 { + margin-bottom: 3rem !important; + } + .ml-md-5, + .mx-md-5 { + margin-left: 3rem !important; + } + .p-md-0 { + padding: 0 !important; + } + .pt-md-0, + .py-md-0 { + padding-top: 0 !important; + } + .pr-md-0, + .px-md-0 { + padding-right: 0 !important; + } + .pb-md-0, + .py-md-0 { + padding-bottom: 0 !important; + } + .pl-md-0, + .px-md-0 { + padding-left: 0 !important; + } + .p-md-1 { + padding: 0.25rem !important; + } + .pt-md-1, + .py-md-1 { + padding-top: 0.25rem !important; + } + .pr-md-1, + .px-md-1 { + padding-right: 0.25rem !important; + } + .pb-md-1, + .py-md-1 { + padding-bottom: 0.25rem !important; + } + .pl-md-1, + .px-md-1 { + padding-left: 0.25rem !important; + } + .p-md-2 { + padding: 0.5rem !important; + } + .pt-md-2, + .py-md-2 { + padding-top: 0.5rem !important; + } + .pr-md-2, + .px-md-2 { + padding-right: 0.5rem !important; + } + .pb-md-2, + .py-md-2 { + padding-bottom: 0.5rem !important; + } + .pl-md-2, + .px-md-2 { + padding-left: 0.5rem !important; + } + .p-md-3 { + padding: 1rem !important; + } + .pt-md-3, + .py-md-3 { + padding-top: 1rem !important; + } + .pr-md-3, + .px-md-3 { + padding-right: 1rem !important; + } + .pb-md-3, + .py-md-3 { + padding-bottom: 1rem !important; + } + .pl-md-3, + .px-md-3 { + padding-left: 1rem !important; + } + .p-md-4 { + padding: 1.5rem !important; + } + .pt-md-4, + .py-md-4 { + padding-top: 1.5rem !important; + } + .pr-md-4, + .px-md-4 { + padding-right: 1.5rem !important; + } + .pb-md-4, + .py-md-4 { + padding-bottom: 1.5rem !important; + } + .pl-md-4, + .px-md-4 { + padding-left: 1.5rem !important; + } + .p-md-5 { + padding: 3rem !important; + } + .pt-md-5, + .py-md-5 { + padding-top: 3rem !important; + } + .pr-md-5, + .px-md-5 { + padding-right: 3rem !important; + } + .pb-md-5, + .py-md-5 { + padding-bottom: 3rem !important; + } + .pl-md-5, + .px-md-5 { + padding-left: 3rem !important; + } + .m-md-auto { + margin: auto !important; + } + .mt-md-auto, + .my-md-auto { + margin-top: auto !important; + } + .mr-md-auto, + .mx-md-auto { + margin-right: auto !important; + } + .mb-md-auto, + .my-md-auto { + margin-bottom: auto !important; + } + .ml-md-auto, + .mx-md-auto { + margin-left: auto !important; + } +} + +@media (min-width: 992px) { + .m-lg-0 { + margin: 0 !important; + } + .mt-lg-0, + .my-lg-0 { + margin-top: 0 !important; + } + .mr-lg-0, + .mx-lg-0 { + margin-right: 0 !important; + } + .mb-lg-0, + .my-lg-0 { + margin-bottom: 0 !important; + } + .ml-lg-0, + .mx-lg-0 { + margin-left: 0 !important; + } + .m-lg-1 { + margin: 0.25rem !important; + } + .mt-lg-1, + .my-lg-1 { + margin-top: 0.25rem !important; + } + .mr-lg-1, + .mx-lg-1 { + margin-right: 0.25rem !important; + } + .mb-lg-1, + .my-lg-1 { + margin-bottom: 0.25rem !important; + } + .ml-lg-1, + .mx-lg-1 { + margin-left: 0.25rem !important; + } + .m-lg-2 { + margin: 0.5rem !important; + } + .mt-lg-2, + .my-lg-2 { + margin-top: 0.5rem !important; + } + .mr-lg-2, + .mx-lg-2 { + margin-right: 0.5rem !important; + } + .mb-lg-2, + .my-lg-2 { + margin-bottom: 0.5rem !important; + } + .ml-lg-2, + .mx-lg-2 { + margin-left: 0.5rem !important; + } + .m-lg-3 { + margin: 1rem !important; + } + .mt-lg-3, + .my-lg-3 { + margin-top: 1rem !important; + } + .mr-lg-3, + .mx-lg-3 { + margin-right: 1rem !important; + } + .mb-lg-3, + .my-lg-3 { + margin-bottom: 1rem !important; + } + .ml-lg-3, + .mx-lg-3 { + margin-left: 1rem !important; + } + .m-lg-4 { + margin: 1.5rem !important; + } + .mt-lg-4, + .my-lg-4 { + margin-top: 1.5rem !important; + } + .mr-lg-4, + .mx-lg-4 { + margin-right: 1.5rem !important; + } + .mb-lg-4, + .my-lg-4 { + margin-bottom: 1.5rem !important; + } + .ml-lg-4, + .mx-lg-4 { + margin-left: 1.5rem !important; + } + .m-lg-5 { + margin: 3rem !important; + } + .mt-lg-5, + .my-lg-5 { + margin-top: 3rem !important; + } + .mr-lg-5, + .mx-lg-5 { + margin-right: 3rem !important; + } + .mb-lg-5, + .my-lg-5 { + margin-bottom: 3rem !important; + } + .ml-lg-5, + .mx-lg-5 { + margin-left: 3rem !important; + } + .p-lg-0 { + padding: 0 !important; + } + .pt-lg-0, + .py-lg-0 { + padding-top: 0 !important; + } + .pr-lg-0, + .px-lg-0 { + padding-right: 0 !important; + } + .pb-lg-0, + .py-lg-0 { + padding-bottom: 0 !important; + } + .pl-lg-0, + .px-lg-0 { + padding-left: 0 !important; + } + .p-lg-1 { + padding: 0.25rem !important; + } + .pt-lg-1, + .py-lg-1 { + padding-top: 0.25rem !important; + } + .pr-lg-1, + .px-lg-1 { + padding-right: 0.25rem !important; + } + .pb-lg-1, + .py-lg-1 { + padding-bottom: 0.25rem !important; + } + .pl-lg-1, + .px-lg-1 { + padding-left: 0.25rem !important; + } + .p-lg-2 { + padding: 0.5rem !important; + } + .pt-lg-2, + .py-lg-2 { + padding-top: 0.5rem !important; + } + .pr-lg-2, + .px-lg-2 { + padding-right: 0.5rem !important; + } + .pb-lg-2, + .py-lg-2 { + padding-bottom: 0.5rem !important; + } + .pl-lg-2, + .px-lg-2 { + padding-left: 0.5rem !important; + } + .p-lg-3 { + padding: 1rem !important; + } + .pt-lg-3, + .py-lg-3 { + padding-top: 1rem !important; + } + .pr-lg-3, + .px-lg-3 { + padding-right: 1rem !important; + } + .pb-lg-3, + .py-lg-3 { + padding-bottom: 1rem !important; + } + .pl-lg-3, + .px-lg-3 { + padding-left: 1rem !important; + } + .p-lg-4 { + padding: 1.5rem !important; + } + .pt-lg-4, + .py-lg-4 { + padding-top: 1.5rem !important; + } + .pr-lg-4, + .px-lg-4 { + padding-right: 1.5rem !important; + } + .pb-lg-4, + .py-lg-4 { + padding-bottom: 1.5rem !important; + } + .pl-lg-4, + .px-lg-4 { + padding-left: 1.5rem !important; + } + .p-lg-5 { + padding: 3rem !important; + } + .pt-lg-5, + .py-lg-5 { + padding-top: 3rem !important; + } + .pr-lg-5, + .px-lg-5 { + padding-right: 3rem !important; + } + .pb-lg-5, + .py-lg-5 { + padding-bottom: 3rem !important; + } + .pl-lg-5, + .px-lg-5 { + padding-left: 3rem !important; + } + .m-lg-auto { + margin: auto !important; + } + .mt-lg-auto, + .my-lg-auto { + margin-top: auto !important; + } + .mr-lg-auto, + .mx-lg-auto { + margin-right: auto !important; + } + .mb-lg-auto, + .my-lg-auto { + margin-bottom: auto !important; + } + .ml-lg-auto, + .mx-lg-auto { + margin-left: auto !important; + } +} + +@media (min-width: 1200px) { + .m-xl-0 { + margin: 0 !important; + } + .mt-xl-0, + .my-xl-0 { + margin-top: 0 !important; + } + .mr-xl-0, + .mx-xl-0 { + margin-right: 0 !important; + } + .mb-xl-0, + .my-xl-0 { + margin-bottom: 0 !important; + } + .ml-xl-0, + .mx-xl-0 { + margin-left: 0 !important; + } + .m-xl-1 { + margin: 0.25rem !important; + } + .mt-xl-1, + .my-xl-1 { + margin-top: 0.25rem !important; + } + .mr-xl-1, + .mx-xl-1 { + margin-right: 0.25rem !important; + } + .mb-xl-1, + .my-xl-1 { + margin-bottom: 0.25rem !important; + } + .ml-xl-1, + .mx-xl-1 { + margin-left: 0.25rem !important; + } + .m-xl-2 { + margin: 0.5rem !important; + } + .mt-xl-2, + .my-xl-2 { + margin-top: 0.5rem !important; + } + .mr-xl-2, + .mx-xl-2 { + margin-right: 0.5rem !important; + } + .mb-xl-2, + .my-xl-2 { + margin-bottom: 0.5rem !important; + } + .ml-xl-2, + .mx-xl-2 { + margin-left: 0.5rem !important; + } + .m-xl-3 { + margin: 1rem !important; + } + .mt-xl-3, + .my-xl-3 { + margin-top: 1rem !important; + } + .mr-xl-3, + .mx-xl-3 { + margin-right: 1rem !important; + } + .mb-xl-3, + .my-xl-3 { + margin-bottom: 1rem !important; + } + .ml-xl-3, + .mx-xl-3 { + margin-left: 1rem !important; + } + .m-xl-4 { + margin: 1.5rem !important; + } + .mt-xl-4, + .my-xl-4 { + margin-top: 1.5rem !important; + } + .mr-xl-4, + .mx-xl-4 { + margin-right: 1.5rem !important; + } + .mb-xl-4, + .my-xl-4 { + margin-bottom: 1.5rem !important; + } + .ml-xl-4, + .mx-xl-4 { + margin-left: 1.5rem !important; + } + .m-xl-5 { + margin: 3rem !important; + } + .mt-xl-5, + .my-xl-5 { + margin-top: 3rem !important; + } + .mr-xl-5, + .mx-xl-5 { + margin-right: 3rem !important; + } + .mb-xl-5, + .my-xl-5 { + margin-bottom: 3rem !important; + } + .ml-xl-5, + .mx-xl-5 { + margin-left: 3rem !important; + } + .p-xl-0 { + padding: 0 !important; + } + .pt-xl-0, + .py-xl-0 { + padding-top: 0 !important; + } + .pr-xl-0, + .px-xl-0 { + padding-right: 0 !important; + } + .pb-xl-0, + .py-xl-0 { + padding-bottom: 0 !important; + } + .pl-xl-0, + .px-xl-0 { + padding-left: 0 !important; + } + .p-xl-1 { + padding: 0.25rem !important; + } + .pt-xl-1, + .py-xl-1 { + padding-top: 0.25rem !important; + } + .pr-xl-1, + .px-xl-1 { + padding-right: 0.25rem !important; + } + .pb-xl-1, + .py-xl-1 { + padding-bottom: 0.25rem !important; + } + .pl-xl-1, + .px-xl-1 { + padding-left: 0.25rem !important; + } + .p-xl-2 { + padding: 0.5rem !important; + } + .pt-xl-2, + .py-xl-2 { + padding-top: 0.5rem !important; + } + .pr-xl-2, + .px-xl-2 { + padding-right: 0.5rem !important; + } + .pb-xl-2, + .py-xl-2 { + padding-bottom: 0.5rem !important; + } + .pl-xl-2, + .px-xl-2 { + padding-left: 0.5rem !important; + } + .p-xl-3 { + padding: 1rem !important; + } + .pt-xl-3, + .py-xl-3 { + padding-top: 1rem !important; + } + .pr-xl-3, + .px-xl-3 { + padding-right: 1rem !important; + } + .pb-xl-3, + .py-xl-3 { + padding-bottom: 1rem !important; + } + .pl-xl-3, + .px-xl-3 { + padding-left: 1rem !important; + } + .p-xl-4 { + padding: 1.5rem !important; + } + .pt-xl-4, + .py-xl-4 { + padding-top: 1.5rem !important; + } + .pr-xl-4, + .px-xl-4 { + padding-right: 1.5rem !important; + } + .pb-xl-4, + .py-xl-4 { + padding-bottom: 1.5rem !important; + } + .pl-xl-4, + .px-xl-4 { + padding-left: 1.5rem !important; + } + .p-xl-5 { + padding: 3rem !important; + } + .pt-xl-5, + .py-xl-5 { + padding-top: 3rem !important; + } + .pr-xl-5, + .px-xl-5 { + padding-right: 3rem !important; + } + .pb-xl-5, + .py-xl-5 { + padding-bottom: 3rem !important; + } + .pl-xl-5, + .px-xl-5 { + padding-left: 3rem !important; + } + .m-xl-auto { + margin: auto !important; + } + .mt-xl-auto, + .my-xl-auto { + margin-top: auto !important; + } + .mr-xl-auto, + .mx-xl-auto { + margin-right: auto !important; + } + .mb-xl-auto, + .my-xl-auto { + margin-bottom: auto !important; + } + .ml-xl-auto, + .mx-xl-auto { + margin-left: auto !important; + } +} + +.text-justify { + text-align: justify !important; +} + +.text-nowrap { + white-space: nowrap !important; +} + +.text-truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.text-left { + text-align: left !important; +} + +.text-right { + text-align: right !important; +} + +.text-center { + text-align: center !important; +} + +@media (min-width: 576px) { + .text-sm-left { + text-align: left !important; + } + .text-sm-right { + text-align: right !important; + } + .text-sm-center { + text-align: center !important; + } +} + +@media (min-width: 768px) { + .text-md-left { + text-align: left !important; + } + .text-md-right { + text-align: right !important; + } + .text-md-center { + text-align: center !important; + } +} + +@media (min-width: 992px) { + .text-lg-left { + text-align: left !important; + } + .text-lg-right { + text-align: right !important; + } + .text-lg-center { + text-align: center !important; + } +} + +@media (min-width: 1200px) { + .text-xl-left { + text-align: left !important; + } + .text-xl-right { + text-align: right !important; + } + .text-xl-center { + text-align: center !important; + } +} + +.text-lowercase { + text-transform: lowercase !important; +} + +.text-uppercase { + text-transform: uppercase !important; +} + +.text-capitalize { + text-transform: capitalize !important; +} + +.font-weight-light { + font-weight: 300 !important; +} + +.font-weight-normal { + font-weight: 400 !important; +} + +.font-weight-bold { + font-weight: 700 !important; +} + +.font-italic { + font-style: italic !important; +} + +.text-white { + color: #fff !important; +} + +.text-primary { + color: #2FA4E7 !important; +} + +a.text-primary:focus, a.text-primary:hover { + color: #178acc !important; +} + +.text-secondary { + color: #e9ecef !important; +} + +a.text-secondary:focus, a.text-secondary:hover { + color: #cbd3da !important; +} + +.text-success { + color: #73A839 !important; +} + +a.text-success:focus, a.text-success:hover { + color: #59822c !important; +} + +.text-info { + color: #033C73 !important; +} + +a.text-info:focus, a.text-info:hover { + color: #022241 !important; +} + +.text-warning { + color: #DD5600 !important; +} + +a.text-warning:focus, a.text-warning:hover { + color: #aa4200 !important; +} + +.text-danger { + color: #C71C22 !important; +} + +a.text-danger:focus, a.text-danger:hover { + color: #9a161a !important; +} + +.text-light { + color: #f8f9fa !important; +} + +a.text-light:focus, a.text-light:hover { + color: #dae0e5 !important; +} + +.text-dark { + color: #343a40 !important; +} + +a.text-dark:focus, a.text-dark:hover { + color: #1d2124 !important; +} + +.text-muted { + color: #868e96 !important; +} + +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + +.visible { + visibility: visible !important; +} + +.invisible { + visibility: hidden !important; +} + +@media print { + *, + *::before, + *::after { + text-shadow: none !important; + -webkit-box-shadow: none !important; + box-shadow: none !important; + } + a, + a:visited { + text-decoration: underline; + } + abbr[title]::after { + content: " (" attr(title) ")"; + } + pre { + white-space: pre-wrap !important; + } + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + thead { + display: table-header-group; + } + tr, + img { + page-break-inside: avoid; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } + .navbar { + display: none; + } + .badge { + border: 1px solid #000; + } + .table { + border-collapse: collapse !important; + } + .table td, + .table th { + background-color: #fff !important; + } + .table-bordered th, + .table-bordered td { + border: 1px solid #ddd !important; + } +} + +.bg-primary { + background-image: -webkit-gradient(linear, left top, left bottom, from(#54b4eb), color-stop(60%, #2FA4E7), to(#1d9ce5)); + background-image: linear-gradient(#54b4eb, #2FA4E7 60%, #1d9ce5); + background-repeat: no-repeat; +} + +.bg-dark { + background-image: -webkit-gradient(linear, left top, left bottom, from(#04519b), color-stop(60%, #033C73), to(#02325f)); + background-image: linear-gradient(#04519b, #033C73 60%, #02325f); + background-repeat: no-repeat; +} + +.bg-light { + background-image: -webkit-gradient(linear, left top, left bottom, from(white), color-stop(60%, #e9ecef), to(#e3e7eb)); + background-image: linear-gradient(white, #e9ecef 60%, #e3e7eb); + background-repeat: no-repeat; +} + +.navbar-brand, +.nav-link { + text-shadow: 0 1px 0 rgba(0, 0, 0, 0.05); +} + +.btn { + text-shadow: 0 1px 0 rgba(0, 0, 0, 0.05); +} + +.btn-primary { + background-image: -webkit-gradient(linear, left top, left bottom, from(#54b4eb), color-stop(60%, #2FA4E7), to(#1d9ce5)); + background-image: linear-gradient(#54b4eb, #2FA4E7 60%, #1d9ce5); + background-repeat: no-repeat; +} + +.btn-secondary { + background-image: -webkit-gradient(linear, left top, left bottom, from(white), color-stop(60%, #e9ecef), to(#dde2e6)); + background-image: linear-gradient(white, #e9ecef 60%, #dde2e6); + background-repeat: no-repeat; + color: #495057; +} + +.btn-success { + background-image: -webkit-gradient(linear, left top, left bottom, from(#88c149), color-stop(60%, #73A839), to(#699934)); + background-image: linear-gradient(#88c149, #73A839 60%, #699934); + background-repeat: no-repeat; +} + +.btn-info { + background-image: -webkit-gradient(linear, left top, left bottom, from(#04519b), color-stop(60%, #033C73), to(#02325f)); + background-image: linear-gradient(#04519b, #033C73 60%, #02325f); + background-repeat: no-repeat; +} + +.btn-warning { + background-image: -webkit-gradient(linear, left top, left bottom, from(#ff6707), color-stop(60%, #DD5600), to(#c94e00)); + background-image: linear-gradient(#ff6707, #DD5600 60%, #c94e00); + background-repeat: no-repeat; +} + +.btn-danger { + background-image: -webkit-gradient(linear, left top, left bottom, from(#e12b31), color-stop(60%, #C71C22), to(#b5191f)); + background-image: linear-gradient(#e12b31, #C71C22 60%, #b5191f); + background-repeat: no-repeat; +} + +.bg-primary h1, .bg-primary h2, .bg-primary h3, .bg-primary h4, .bg-primary h5, .bg-primary h6, +.bg-success h1, +.bg-success h2, +.bg-success h3, +.bg-success h4, +.bg-success h5, +.bg-success h6, +.bg-info h1, +.bg-info h2, +.bg-info h3, +.bg-info h4, +.bg-info h5, +.bg-info h6, +.bg-warning h1, +.bg-warning h2, +.bg-warning h3, +.bg-warning h4, +.bg-warning h5, +.bg-warning h6, +.bg-danger h1, +.bg-danger h2, +.bg-danger h3, +.bg-danger h4, +.bg-danger h5, +.bg-danger h6, +.bg-dark h1, +.bg-dark h2, +.bg-dark h3, +.bg-dark h4, +.bg-dark h5, +.bg-dark h6 { + color: #fff; +} + + * + + o + + o + Aa + Sans-serif + + Aa + Serif + + ------------------------------------------------------------------------ + + Aa + + ------------------------------------------------------------------------ + + ------------------------------------------------------------------------ + + ------------------------------------------------------------------------ + Light + + Dark + + Sepia + + o + @keyframes grow { 0% { transform: scaleY(1); } 15% { transform: + scaleY(1.5); } 15% { transform: scaleY(1.5); } 30% { transform: + scaleY(1); } 100% { transform: scaleY(1); } } #waveform > rect { + fill: #808080; } .speaking #waveform > rect { fill: #58bf43; + transform-box: fill-box; transform-origin: 50% 50%; + animation-name: grow; animation-duration: 1750ms; + animation-iteration-count: infinite; animation-timing-function: + linear; } #waveform > rect:nth-child(2) { animation-delay: + 250ms; } #waveform > rect:nth-child(3) { animation-delay: 500ms; + } #waveform > rect:nth-child(4) { animation-delay: 750ms; } + #waveform > rect:nth-child(5) { animation-delay: 1000ms; } + #waveform > rect:nth-child(6) { animation-delay: 1250ms; } + #waveform > rect:nth-child(7) { animation-delay: 1500ms; } + + o + + Voice: Default DefaultEnglish (en-029)English (en-GB)English + (en-GB)English (en-GB)English (en-GB)English (en-GB)English + (en-US)English (en) + diff --git a/www/css/joint.css b/www/css/joint.css new file mode 100644 index 0000000..38a34d1 --- /dev/null +++ b/www/css/joint.css @@ -0,0 +1,387 @@ +/*! JointJS v2.1.2 (2018-05-08) - JavaScript diagramming library + + +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ +/* +A complete list of SVG properties that can be set through CSS is here: +http://www.w3.org/TR/SVG/styling.html + +Important note: Presentation attributes have a lower precedence over CSS style rules. +*/ + + +/* .viewport is a node wrapping all diagram elements in the paper */ +.joint-viewport { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} + +.joint-paper > svg, +.joint-paper-background, +.joint-paper-grid { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +/* +1. IE can't handle paths without the `d` attribute for bounding box calculation +2. IE can't even handle 'd' attribute as a css selector (e.g path[d]) so the following rule will + break the links rendering. + +path:not([d]) { + display: none; +} + +*/ + + +/* magnet is an element that can be either source or a target of a link */ +[magnet=true]:not(.joint-element) { + cursor: crosshair; +} +[magnet=true]:not(.joint-element):hover { + opacity: .7; +} + +/* + +Elements have CSS classes named by their types. E.g. type: basic.Rect has a CSS class "element basic Rect". +This makes it possible to easilly style elements in CSS and have generic CSS rules applying to +the whole group of elements. Each plugin can provide its own stylesheet. + +*/ + +.joint-element { + /* Give the user a hint that he can drag&drop the element. */ + cursor: move; +} + +.joint-element * { + user-drag: none; +} + +.joint-element .scalable * { + /* The default behavior when scaling an element is not to scale the stroke in order to prevent the ugly effect of stroke with different proportions. */ + vector-effect: non-scaling-stroke; +} +/* + +connection-wrap is a element of the joint.dia.Link that follows the .connection of that link. +In other words, the `d` attribute of the .connection-wrap contains the same data as the `d` attribute of the +.connection . The advantage of using .connection-wrap is to be able to catch pointer events +in the neighborhood of the .connection . This is especially handy if the .connection is +very thin. + +*/ + +.marker-source, +.marker-target { + /* This makes the arrowheads point to the border of objects even though the transform: scale() is applied on them. */ + vector-effect: non-scaling-stroke; +} + +/* Paper */ +.joint-paper { + position: relative; +} +/* Paper */ + +/* Highlighting */ +.joint-highlight-opacity { + opacity: 0.3; +} +/* Highlighting */ + +/* + +Vertex markers are `` elements that appear at connection vertex positions. + +*/ + +.joint-link .connection-wrap, +.joint-link .connection { + fill: none; +} + +/* element wrapping .marker-vertex-group. */ +.marker-vertices { + opacity: 0; + cursor: move; +} +.marker-arrowheads { + opacity: 0; + cursor: move; + cursor: -webkit-grab; + cursor: -moz-grab; +/* display: none; */ /* setting `display: none` on .marker-arrowheads effectivelly switches of links reconnecting */ +} +.link-tools { + opacity: 0; + cursor: pointer; +} +.link-tools .tool-options { + display: none; /* by default, we don't display link options tool */ +} +.joint-link:hover .marker-vertices, +.joint-link:hover .marker-arrowheads, +.joint-link:hover .link-tools { + opacity: 1; +} + +/* element used to remove a vertex */ +.marker-vertex-remove { + cursor: pointer; + opacity: .1; +} + +.marker-vertex-group:hover .marker-vertex-remove { + opacity: 1; +} + +.marker-vertex-remove-area { + opacity: .1; + cursor: pointer; +} +.marker-vertex-group:hover .marker-vertex-remove-area { + opacity: 1; +} + +/* +Example of custom changes (in pure CSS only!): + +Do not show marker vertices at all: .marker-vertices { display: none; } +Do not allow adding new vertices: .connection-wrap { pointer-events: none; } +*/ + +/* foreignObject inside the elements (i.e joint.shapes.basic.TextBlock) */ +.joint-element .fobj { + overflow: hidden; +} +.joint-element .fobj body { + background-color: transparent; + margin: 0px; + position: static; +} +.joint-element .fobj div { + text-align: center; + vertical-align: middle; + display: table-cell; + padding: 0px 5px 0px 5px; +} + +/* Paper */ +.joint-paper.joint-theme-dark { + background-color: #18191b; +} +/* Paper */ + +/* Links */ +.joint-link.joint-theme-dark .connection-wrap { + stroke: #8F8FF3; + stroke-width: 15; + stroke-linecap: round; + stroke-linejoin: round; + opacity: 0; + cursor: move; +} +.joint-link.joint-theme-dark .connection-wrap:hover { + opacity: .4; + stroke-opacity: .4; +} +.joint-link.joint-theme-dark .connection { + stroke-linejoin: round; +} +.joint-link.joint-theme-dark .link-tools .tool-remove circle { + fill: #F33636; +} +.joint-link.joint-theme-dark .link-tools .tool-remove path { + fill: white; +} +.joint-link.joint-theme-dark .link-tools [event="link:options"] circle { + fill: green; +} +/* element inside .marker-vertex-group element */ +.joint-link.joint-theme-dark .marker-vertex { + fill: #5652DB; +} +.joint-link.joint-theme-dark .marker-vertex:hover { + fill: #8E8CE1; + stroke: none; +} +.joint-link.joint-theme-dark .marker-arrowhead { + fill: #5652DB; +} +.joint-link.joint-theme-dark .marker-arrowhead:hover { + fill: #8E8CE1; + stroke: none; +} +/* element used to remove a vertex */ +.joint-link.joint-theme-dark .marker-vertex-remove-area { + fill: green; + stroke: darkgreen; +} +.joint-link.joint-theme-dark .marker-vertex-remove { + fill: white; + stroke: white; +} +/* Links */ +/* Paper */ +.joint-paper.joint-theme-default { + background-color: #FFFFFF; +} +/* Paper */ + +/* Links */ +.joint-link.joint-theme-default .connection-wrap { + stroke: #000000; + stroke-width: 15; + stroke-linecap: round; + stroke-linejoin: round; + opacity: 0; + cursor: move; +} +.joint-link.joint-theme-default .connection-wrap:hover { + opacity: .4; + stroke-opacity: .4; +} +.joint-link.joint-theme-default .connection { + stroke-linejoin: round; +} +.joint-link.joint-theme-default .link-tools .tool-remove circle { + fill: #FF0000; +} +.joint-link.joint-theme-default .link-tools .tool-remove path { + fill: #FFFFFF; +} + +/* element inside .marker-vertex-group element */ +.joint-link.joint-theme-default .marker-vertex { + fill: #1ABC9C; +} +.joint-link.joint-theme-default .marker-vertex:hover { + fill: #34495E; + stroke: none; +} + +.joint-link.joint-theme-default .marker-arrowhead { + fill: #1ABC9C; +} +.joint-link.joint-theme-default .marker-arrowhead:hover { + fill: #F39C12; + stroke: none; +} + +/* element used to remove a vertex */ +.joint-link.joint-theme-default .marker-vertex-remove { + fill: #FFFFFF; +} +/* Links */ + +@font-face { + font-family: 'lato-light'; + src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAHhgABMAAAAA3HwAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAABqAAAABwAAAAcaLe9KEdERUYAAAHEAAAAHgAAACABFgAER1BPUwAAAeQAAAo1AAARwtKX0BJHU1VCAAAMHAAAACwAAAAwuP+4/k9TLzIAAAxIAAAAWQAAAGDX0nerY21hcAAADKQAAAGJAAAB4hcJdWJjdnQgAAAOMAAAADoAAAA6DvoItmZwZ20AAA5sAAABsQAAAmVTtC+nZ2FzcAAAECAAAAAIAAAACAAAABBnbHlmAAAQKAAAXMoAAK3EsE/AsWhlYWQAAGz0AAAAMgAAADYOCCHIaGhlYQAAbSgAAAAgAAAAJA9hCBNobXR4AABtSAAAAkEAAAOkn9Zh6WxvY2EAAG+MAAAByAAAAdTkvg14bWF4cAAAcVQAAAAgAAAAIAIGAetuYW1lAABxdAAABDAAAAxGYqFiYXBvc3QAAHWkAAAB7wAAAtpTFoINcHJlcAAAd5QAAADBAAABOUVnCXh3ZWJmAAB4WAAAAAYAAAAGuclXKQAAAAEAAAAAzD2izwAAAADJKrAQAAAAANNPakh42mNgZGBg4ANiCQYQYGJgBMIXQMwC5jEAAA5CARsAAHjafddrjFTlHcfxP+KCAl1XbKLhRWnqUmpp1Yba4GXV1ktXK21dby0erZumiWmFZLuNMaQQElgWJ00mtNxRQMXLcntz3GUIjsYcNiEmE5PNhoFl2GQgzKvJvOnLJk4/M4DiGzL57v/szJzn/P6/53ee80zMiIg5cXc8GNc9+vhTz0bna/3/WBUL4nrvR7MZrc+vPp7xt7/8fVXc0Dpqc31c1643xIyu/e1vvhpTMTWjHlPX/XXmbXi3o7tjbNY/O7pnvTv7ldm7bvh9R/eNKzq658Sc385+Zea7c9+avWvens7bZtQ7xjq/uOl6r+fVLZ1fXP5vuqur6983benqao0587aO7tbf9tHYN6/W+N+8XKf9mreno7s1zpVXe7z26+rjS695e2be1hq3pfvS39b/7XcejTnNvuhqdsTNzZ6Yr97i/+7ml7FIXawuwVLcg/tiWdyPHi4+rD7W/Dx+3RyJXjyBZ/AcVhlrNdZivXE2YAgbMYxNeBM5Y27FNmzHDuzEbuxzjfeMvx/v4wN8iI8wggOucxCHcBhHkGIUYziKAo7hODJjnlDHjXuKrjKm9HsO046rOI+Fui/rvKzzss7LOi/rsqbLmi5ruqzpskZ9mfoy9WXqy9SXqS9TX6auRl2Nuhp1Nepq1NWoq1FXo65GXY26GnU16srU1WJJzKJnLjrbczJIzTg149SMUzNOzXgsa/bGfbi/mY+e5uvxsOMVzXXxYrMUL6krnbvKuYPqanWNulbNOXcrtmE7dmAndmOfcTJ1XD3lu2Wcdt4ZnEWl7dMgnwb5NBgX/f8DanskqEJxD8U9kjQoRYNSVJGgymWlWyitxQPNk9Qm8WBzkuItVPZQ2ENdKyUVKalISUVKKlJSkZKKlFQoS6hKqOmhpjVrgxT1UNRj9lpKeuKVmCWPc5p7Y67aia7mI/zbQs0j1OyN7zVHYyFul97u5gR1e/k6wdeJuLP5Gm8neDsh05vN9mazvdlsb44nm9X4TfONeNq5fXjGe8+qz6nPqy80t8cfqPyj4xXN6Ugcv6S+3CzESjpW0TCovuHz1Y7XOF6rrnf9DRjCRgxjE95Ejo6t2Ibt2IGd2I33XHc/3scH+BAfYQQHcBCHcBhHkOJj1x5Vx3AUBRzDcXzisyI+xWfIXOOE90/RWMZpes9gio9nVXPK9UdkYYssbJGFLXHRe92y8KUZqMrCl/Edee5UuyRqPm7x/iIsaw7Jw4QsVGXhiCyksjARv/T9fqx0ziDWYL3vbMAQNmIYm/Am9jl3HKd97wymXOOsWsE5xxfVn1HUR00fJX2yUInbvdvt7MVYgju9lqr3tJXl4l5n3sf/+5sZdQOU7TWnBfNpLo2xyhiD6mp1jbpWzTl3K7ZhO3ZgJ3bjLeO9jT3Y277HBvhbpXyAvxX+VnTQp4M+6vuo7+Nrha8VvlZ00Rc3Ut7vyv2u2u+K/c7sd2a/b/b7Zr9v9sddnM9xu5fbvdzOyXsm75m8L+R8TsbvkOtUrlO5TuU5k+dMnlN5zuQ5ledMjjNZzbif436O+znu57if436O+zk5S+UslbNUzlI5S+UslbNMzlI5S+UslbNUzlI5S+Usk7NMzjI5y2QsNWu9ZqvX/TqHO11Wr/m4xfEirMcGDGEjhrEJb2LK987hp9w5+a05vTKfv25e0OsFvV5wD0/o84IeL7hXC+Z03Fo5bl7HOXuSsyc5e/Kac3nAuQdxCIdxBClGMYajKOAYjqM1zyfUU8YtYxpVnMevYtZXEzEXneiKe3SxMOart+upW64XYwmW4h4sa74gmX2S+bpkLpPMPh1O63Bah9O6m9bdtM7e0dkRnb0TK429yriD6mp1jbpWzfl8K7ZhO3ZgJ3Zjn7EPGOcgDuEwjiDFKMZwFAUcw3Fkzjuhjjv3lPHLOO1aZzClp7NqBeccT/usivO46L07zPywmb/VzN9q5ofN/LCs9lmHSzqs6rCqw6oOqzqsSsWwVAxLxbBUDEvFsFQMS8WwtbFkbSxZG0vWxpK1sWRtLFkbS7qq6qqqq6quqrqq6qqqq6quqrqq6qqqq6quWnNXlbJbpYwuczJpTibNyaQ5mTQnk+ZkwopR5eckPyf5OcnPSX5O8nOSn5NWgKoVoGoFqFoBqryajGe+vldv/tb9mrhfE1caat+vi9UluLO51BWHXHEoHvvqfzzp5kk3T7o9l+51Hyfu44Q/3e7jhEfd7uPEc+kh93IiEb0SMeC59Gep6PVcGpKKXvd4IhW9EtF7zXs95/tbsQ3bsQM7sRvv0bMf7+MDfIiPMIIDdBzEIRzGEaT42HVH1TEcRQHHcByf+KyIT/EZMtc44f1TNJZxZb2YRhXn8fDlJ3/xqid/nrM1zuY5W7QC/pCjRU7ul6pRDtY5WOdgnYO7OVfnWp1jZy4/sWvtJ/Zq9dLTusahIoeKHCpyqMihIoeKHCpK3ajUjUrdqNSNSt2o1I1K3SgX6lyoc6HOhToX6lyoc6DOgToH6hyoc6DOgbpu67qt6bZ21ZM3f9WTN6/7mu5ruq+1n7wvc2ABBwY4sIADCzjwOgcSDrzOgQHZystWvu1Ea3VZ5L0rK8ylfF1aZS7tfRLuJNxJuPOCfOXlK8+lRL7ynErkK8+tf8lXXr52ydeIfK2Tr10cXMDBhIMLZCzPxYSLC7iYcHGAiwNcHODiABcHuDjAxYFrrkrX3vMkHE44nHA44XDC4UTO8lxOuJxwOeFywuWEy4mc5eUsL2d5OctfXsESziect9Ok9wym+HdWreCc42mfVXEeF733Ey6nl10tcLTA0QI3C9wscLLEyRInS9wrca7EtTLHJjjVWptT7qScSXVf0H1B9wXdF3Rf0H1B9wUdlnRY0mFJhyUdlnRY0l1JdyXdlXRX0l1JdyXdFHRT0k2qm5TqlOqU6lQ6ZrXuFHRihQS92PwvNTX7m6K9TdG+pmhPUrQnKdqTFO1JivYhxfiuM0ecOWJvV3P2iOfRZs+jumfRZvu3mtEaUpAZrWEv1xpxxIgjRhwx4ogRR4w4YsQRI47ETXK7XGaXU7W8ndlWXlc6HsQanMYZXJqH5eZheXseLqrz+ZvxN+NvaxfT2sFkvMp4lfEq41XGq4xXrV1JxquMVxmvMl5lvGrtQrKY59rrXHtd+5lzrWfIlO+cw/fdbYWvz7rF8aL2fDfoadDToKdBT0PiCxJfkPiCxBckviDxBYlvzWuD1gatDVobtDZobdDaoLVBa4PWBq0NWhu0Nr5WcP3Xu6UrO6EZ8So/5+qm047iZv54asWiWBw/ih/b594Vd8fS+Lln8C+sGff6LX9/POC30IPxkDX0sXg8nogn46n4XTwdfZ5Rz8bzsSJejCReij+ZlVUxYF5Wm5e1sT42xFBsDE/eyMV/Ymtsi+2xI3bGW/F27Im9fr2/E+/F/ng/PogP46PwWz0OxeE4Eh/HaIzF0SjEsTgen8cJv8hPRdlcn7FbOGuOz8V0VON8XPw/fppwigAAAHjaY2BkYGDgYtBh0GNgcnHzCWHgy0ksyWOQYGABijP8/w8kECwgAACeygdreNpjYGYRZtRhYGVgYZ3FaszAwCgPoZkvMrgxMXAwM/EzMzExsTAzMTcwMKx3YEjwYoCCksoAHyDF+5uJrfBfIQMDuwbjUgWgASA55t+sK4GUAgMTABvCDMIAAAB42mNgYGBmgGAZBkYGELgD5DGC+SwMB4C0DoMCkMUDZPEy1DH8ZwxmrGA6xnRHgUtBREFKQU5BSUFNQV/BSiFeYY2ikuqf30z//4PN4QXqW8AYBFXNoCCgIKEgA1VtCVfNCFTN/P/r/yf/D/8v/O/7j+Hv6wcnHhx+cODB/gd7Hux8sPHBigctDyzuH771ivUZ1IVEA0Y2iNfAbCYgwYSugIGBhZWNnYOTi5uHl49fQFBIWERUTFxCUkpaRlZOXkFRSVlFVU1dQ1NLW0dXT9/A0MjYxNTM3MLSytrG1s7ewdHJ2cXVzd3D08vbx9fPPyAwKDgkNCw8IjIqOiY2Lj4hMYmhvaOrZ8rM+UsWL12+bMWqNavXrtuwfuOmLdu2bt+5Y++effsZilPTsu5VLirMeVqezdA5m6GEgSGjAuy63FqGlbubUvJB7Ly6+8nNbTMOH7l2/fadGzd3MRw6yvDk4aPnLxiqbt1laO1t6eueMHFS/7TpDFPnzpvDcOx4EVBTNRADAEXYio8AAAAAAAP7BakAVwA+AEMASQBNAFEAUwBbAF8AtABhAEgATQBVAFsAYQBoAGwAtQBPAEAAZQBZADsAYwURAAB42l1Ru05bQRDdDQ8DgcTYIDnaFLOZkMZ7oQUJxNWNYmQ7heUIaTdykYtxAR9AgUQN2q8ZoKGkSJsGIRdIfEI+IRIza4iiNDs7s3POmTNLypGqd+lrz1PnJJDC3QbNNv1OSLWzAPek6+uNjLSDB1psZvTKdfv+Cwab0ZQ7agDlPW8pDxlNO4FatKf+0fwKhvv8H/M7GLQ00/TUOgnpIQTmm3FLg+8ZzbrLD/qC1eFiMDCkmKbiLj+mUv63NOdqy7C1kdG8gzMR+ck0QFNrbQSa/tQh1fNxFEuQy6axNpiYsv4kE8GFyXRVU7XM+NrBXbKz6GCDKs2BB9jDVnkMHg4PJhTStyTKLA0R9mKrxAgRkxwKOeXcyf6kQPlIEsa8SUo744a1BsaR18CgNk+z/zybTW1vHcL4WRzBd78ZSzr4yIbaGBFiO2IpgAlEQkZV+YYaz70sBuRS+89AlIDl8Y9/nQi07thEPJe1dQ4xVgh6ftvc8suKu1a5zotCd2+qaqjSKc37Xs6+xwOeHgvDQWPBm8/7/kqB+jwsrjRoDgRDejd6/6K16oirvBc+sifTv7FaAAAAAAEAAf//AA942sR9B2Ab15H2vl0sOha76ABJgCgESIIESIAECPYqik2kSFEiqS5Rnaq2bMndlnvNJU7c27nKjpNdkO7lZPtK2uXSLOfuklxyyd0f3O9c7DgXRxIJ/fPeAiRFSy73N9kktoDYeTPzZr6ZN29A0VQnRdGT7CjFUCoqIiEq2phWKdjfxSQl+7PGNEPDISUx+DKLL6dVysLZxjTC1+OCVyjxCt5OujgbQPdmd7Kjp5/rVPw9BR9JvX/2Q3ScPU4JlIdaQaWNFBWWWH0mbaapMBKLoyJ1UtJaM/hn2qql1GHJZMiIpqhYEJescOSKSV4UlqwmwSQZ2VSKksysYBJdqarqZE0zHY+5aauFo/2+oFmIC3Ck8keY9zmnz2r2u4xGl99cmohtpBkl0wE/9GD+qsXn4hJMHd0792JkeHRDKrVhdBjT+zLzOp0AerWUlaqiYIBUWNTHZ1R6SqMIi6YYEm2EZobPiAwv6YA2js9IdhSmqqoxCSoOATGhkoXDl0c1NGfieBp5ckeM4ioUzr77kGCxCA/NHxF+jVGUYjU8P0HVoyEqHQN+iSXxtBHokHhzPD5To4gZDeFp1pOsC9jjUo0yMx2oqIwH7LEZrYrcUrpT9fiWFm7pBJMTbiGxISqWnZRKjJl0SZk2PN1a4tPAB/OSGQZgM2akRhQWE65Xmx/7ww8pa1grxiKcqD8hRdSnWJE/8WrzbX+YItdNcB3+LIyvm3jJqT4lxvhpNqY3w4PJbx3+LUb4aSHCm/Ezpt0lTrjuIb8D+LcY5qcrwib5bZXkbfAh8fwfJskVeE8dfs90Kv/OenydodL6cAT+oVYrq9TpeRih2xMIV1RGYvFkXao+cr5/YqsLy6cRtaC42ZtM2OPmZtSAGK85HrNaVExcpQz5GThWeRmQWW1N0uxlOBRGZjgr8Zq9YzTzL6uyc0pF+T+NK5ym8GZUvTlcjMb/XcmWvbHqf3jY7H9tKufMaCz7D2OsUwhveo0TUAJVr8r+A/oNq9Xy6K6QD6GHzZZsA/obj1qR3Q7n2YOuymy9IKgU6L7sVrsJ/a2hHt1FwSx8MHtK4VceoxqoZdRK6m+ptBVrIkyKdk1GDIJAh6Mif1JqFDJiIy/VgRRrOBB3TZ06PLOSo4pBWUMxsYaX+uFWRMhII7KAW/5j9hksSIUYAkm6Tkht7CnRdoKdtrbZgMshfrog5AKmB/FvsY2fbsfXGWra5gq1Eba/aLW5CoJt7QuclRpBCKIyJenq4FWbklbWwGt3SuwXRH9KjJgkrxtmblV1C0rAhFXYzRGmFiZvC8IyULmRXaX0+yJ0iHGzeDIbEeZ8MoLMFjdtN3MMaob3w/0HC/SCpjBU2z2R8i67fkdr7c57tmiQ0Vii3/Fgm13L68taN3a4q7aM99cVN+5/fKceGQ0l+mPvjFau2J4qWnHxihBKDl+zprJm9f7m50uNNl9pwMXQt9lqR46u7z62s4X5Omf+vmqg1S94y4Ls3EtGX1nt8g1NYw9e0s3+1GD+s3KS+X3L2taIha5VVA9sOfPXbN3aI12d69srzBTFUuNnf89+m32FMlMhsB2dMJe/TKVLYQanW7HZ62Uz6QqQYprFk9nPZmZWJVpZQ1haBYdOIzl0shkkjhMLYzFmRAsvuUF+WjjU8lI1HHbBYRcvDcJhA0zbCXh1WwRT2siWplIpabALjhOtlSlsKVf1gtFsqIbLficcaakUWE3zOVYzQieBx/FYM40Z7PdxtJkIBSn96DPeOB4dPtDSsn+kqnrVvuaWA8PRwUDTcCQy0hIItIxEIsNNgTKFUWnius783mCjV1atPNAK745Wj+xvajm4smpFoHk4GhlpCgSa4N0jzQHFwMQtayORtbdMjN+MX28eHzzQ7fN1HxgcPNDj8/UcODPJ3qPWnt5lQmMTt6yLRNbhd05EIhPwzv3Lvd7l+wcHDy33+ZYfAju69+wH7GGQRSs1TF1HpeNYCo1YCstUmbQBC8ANB24D2ELKbdOALxohXG8Dn9PGS2rgqx/mlh9MHByawNqDtSvHcwms/Sp4dfoF04yBbVy2ImBPiSZB7EuJ5aZ0qDpJeO9eBrcpdXUS35a5Dgpdm+OpXYk1PhiKMJiTVovNDlxPYsZzSIWdRhRxzGKmJ1EwxDF7a9dd3dvTU7P5xpGuy9YmaU7vMKg5RuVvHG9s2ra8dPVa9K1IUk3r9Sm6qwVVrzU5+B9F9l37lZUDX71k+dbGzYfrl199YH0oW65kO/f2l6GLem/cP1Y4fP/Y8ssm4tGhXSlGwRp0BV3N4WDXhrpV949lm3of7TMYN31vffZdtfHvayfaAvGtf7Fl8PBgyNswWI3+nlUVDW0+CK6LQth3IgPxnX7Zc+bcJhJ1eZ9JfvRLneW8h1zkF+HzvpH9kEbKAsoJMwqJLvIZBvj7AvnvMUvtNrDeSuCgCR8ZUYT5hrttajBsUF12xRWXq7jw4FSbm77hyL/+8tdHC1RGre5vsmv//d+ya/9apzWqXUf/9Ze/gudMZj9EL5HnJOTnaE+KVGzGIJtRAy+xsgrgB0sGLcwwWm0HKYusIDLYrtlrkglTbQ0dCoZqWpCbwVNGFQpOqi+//IqjKsSFV0y1FxW1T60Ic7/Q6v4aPflv/46e/BudllMXHP31L//1yJFf/fLXR1wqzMOrmHvoNHuKqqWSlFgSndHoKRXmYCIqlpyU1LFYbCZA6JK09lhMSgJFgRLBNM1yxWWgaZgvSTtY1AhqQnGrRalqBpdnBz6DmfUgVSiCQm5UhPy1NYkkh4woBFoHihm6quAt3sKpVbWsWm/l33KdMBaYTC7+Lec7RqtBiS/rbMYTrrc4l9ns4tiByEGt2WR2m/75n0xus2DRHIgc0GhpRqM+ED2oEQRTgfDP/yQUCEZBs7/ygFrDMFo10ZED1CuKasVfUjqYlyIVFVVxCSkzIhtLUwjjEkqrCacRhQ8Rg6elnoiDjkkasHyKWFqjxfc0KnibVoMPtZQGpCKrRK0XlMpr9Qp+4QB6eQi9ku0eom/pQ9/PxvqyVegHsp4ezM6hIPUNqoCKU2knNgqMHsxuIVYwkQPIC3gU/xQBc5UUuDIbTGjGSXwchp3gxGw5EWM2NjNJosYHq0srqmxlKb9RrVRoi4udCqVRE6xaE4g3VpePjazwGtVaVqvQlibbSmg6LtOynU7QHfQt4PF9mB8S0mTwDxIVUYlC4RnGimcQ1kB5fNbt6Od0YmQE/+0UYOsyGIdAlS1C1vkDhFH0ArrGSI/6BGieOhcpnwuP4Rlnz5x9lv5H9keUmjJSIhNFoiYqacknqVAC/ASMnKWvNJaWz12v9gqrlXTwNGWxUATL9p39UDGe84edOQqdmkzO/6mBwlLZ0xkWPJ05I5XlfFoO75/ju0zNCKhHJquFxjyPoE+4pb6Vd7w+NfXGHcPDd7y5Z+r1O1ZOdh66d9Wqew915l/pd99E9hfHx1/MZt58M5vBR8j+pnTqkeXLHzkliacf6el55DTm7yxg8RD7TYqnAIkrMfUqFaD+GLFt05wSqUE/haioBtNmyKQZNVZHhgXNVDP4UK0EzTTBaBg16A6CsSAODnR4JIjoKehrTRJ8rS80ix7vQ01zVjTAZN/SwrRRNKFDpx/q71fc4w9lfwNmAFHXAz1h4GeMWk+lKUxPpTaT9mBuGrHKxKOiS+ZmeSztsmASXDA5MG+12E4YMlIN5jHmLevBvK0E7ZYU5WDKjMI0a3MFiLOKY63OYS7MUuKr/KFmJq84KvBWcW/MVoSu12nQfzbtGqioHb+4teui8Xq91kMr6Wr9wOH7xkfuuagjtvpQc7be2x2gD/IWv86hRv/VfPjSK7qHLukPlPfubAog9fovT9ZUbf7y1uHbr72sJVutVpv5FJkb15/9QBGF8S6nbqfSnXi8HGgP14kHxoFxSMeIImkAPTk6Y3n01BMVK09KpcCFUlmnkiAbdxL/kdsB3HDzorn4pCC1ADt64XZpJfCAUQMP3MI0F2vsxGZUcoCkJKoFrjoFsTEl+k3p8krs2rGBxQbAg9zsvN7VnsusKFrEKzfKI6jrQ3q9zsKqlbZA7cDOjnW3rY+Ub3nskg1f2lQdX31Rc9dFYw2c2q1iY4b+w/ePj3zlQGvFwM6mRx9ffuXxySue3N2Atgis1mgxJesbIoVNGy9Jdlw0XL2Mjgztbx842Osr69nZkmMnxkbdh1bXG92v3TF+7/7m9j3Xw3xsA/05yj4H+myjeqm0DmMi4qYNgg4ZwiITlwyg4GqILuxRUXcSwl1JC8gHjK8D640up8WCAQ6olIgEsIx5XbYowwjMrhfceRK0OpFso3+6BmkMxt+NzY0aBWYzvZdm0G+Zd2Y7EjpDdhN61KBL0H8SSi1E1veCrBWAHaLUP1HpMJa1msmk7VjARdrMjNcUtgOF5rjkVWfEYqCwKioaTkpBEGJ1LnSd+yOJbEQ7BDYQ0UhFmlOc6D7xquFXb92Ib7BicURyF6nhGiuZbXDTekK08tMWq9kcflX7lRO/gnfpQD+mPe5iczgNv4tvLb7VrwRVSKXhXfBCzVhtbosnIgegGqvNXuQ2WzzFiwNNBFSB8jiceIaZYOqnKSZINEeOfxaZK6UqZMas83sZYtjmwfa9hVqLITY41b3qy3uaIuvv2lR/fU/rIfq2AvfcH9d0XVZ38OsXNwzd/OKOxr2bhg6WGj0l7sT2ezauOLa+BpvG68othdkiwdh68aMbLnrh6g5rIIrt8W3A4yrgcSFEJ2DRHJjLPnUmrcQ6wFU4lDCFOCVMoWpilotgChXxUghEbwY2x+A1VARQQ8c5VGSOVPjw2Mw6eVZgmyF7BNW5Y1lqoW9bvRXdJvhXZ4eKa22NT29Z//Ch1u4rpV3bnjnSvjG+7oaRsTsma2s2HRuauHNLDfr70ZM30BbH3PfKewPN3U0HHt665amjHW2XS2Mrb9maTG6+cXDkxvXxlq1Xy/70BtDxHpJvci3ScMmoJf4w5wSxHwVoRMJMlEiCzt7A/LVKObdTXWhvpx8ymGbf0PHs7pYKwaU5/TPeynoKrDz+fIa6HHhYBjYpBJH5IPUmlfYTOwyxBEnR9CkzM21JvxF0tS4utangqUOEmbI9Ehux5dHCsTYqNcomCvPVbchMW9wxNYQncHFZFBtxaaWs18Lzb1+J1ZcTWV7sOCGl7KdEJwTsdSknCcxZZ6qDqOMM66yTD0lQvqwRZGX0VyaJrJLYyrnBi0p9bXBk0abmoxKmdhEmUMno9byR4ZLzyyOrLu5q2drur9/7wOZND+xt8HduaVl20arosiue37nzG5cvm6zdcsvIyM1bEsv2Hmtqun5qWTQ4dNmqkcuGSsLDRwYGjo6E0dVDV65r4k2tY3uaB26aTKUmb+5vmhprNRmb1105tO7uncnkzrvX91wyGo2OXtKz8er+4uL+q+md9XtHY7HRqYbmqaHKyqEprNsiyD0GcnGDdwTdNlP5ODuizsy4AmYcXLtUspMEcXiAzR6eQA1tzi2WeTCMtrvMhF+RAOi2lrKnlsbMKgSGDkdrBH98gkli1+XHJzc9dnGrPdJenr3e6B9DX/fUWBuObxq/Z2/z5tj4Vf1rbtlQFV93Vd/QjRsTCuX6Rw63tx15envdju1TTXM/dtCrwwOB9uUNU/dNDl0zHm3cdKRpEKZ1fN01BFPdDZhvmPkF6LefqlxAfaI3Ktkx5gsQEIsNtzUjFpIXqeR8yE849/Ru42IgmDz3bEnWdGwJSiR0AaaW6aqkOnIW3Ap0GaMyFo1ERdNJiSqGmMUBlGnJixQFvjtM8+kLSrKGwbU4PpGmCJovBLqX0K08PwZnrj6H5DnqUzH5E8jIPKEYBD9JmWsRsRRKFYToOHB6gqH0/Nx3fKVhD50wGugHytGtHTpek/1XQavhs79UC7oOzI9n0X8yp5jLSD7dJSN7CHMA1LNYCdVRSTNviRD8PMsMzkrMIPrPvj7U2t9P6IB/RgWS6UAEkiVwpIaCTQhZEdIb6WRxmSUgzH27gKGQsUNnUqFiXsNyauTmbB3ZS8qBDt/ZD+kfwLwopeqpKSpdh+US0ecwuBdj8IaoaD4pmTic4Zi2m+IcTAWQUFlUiltJ1qMQTxKBpIglkxlPEm+kDic94oLIp8RCAOrE1XkjcI/SmoJyxmMeAimMyB8CG6PIzxGAu0vE6yvsGtlSv/yqTXVVvav7amh9B1vdM9pTHe7dVNu5pTOkMqpf5FzeRZEKGy6Ml9rDQxctX3FgtK2u3vfMN9nylsamgcmu5Jomj78ioD8zcB493X9WryxlR6gV1Gbq25TYG5Va2Ey6pRfDw5ZOgIfGqGiNS2FFRlwVE9dHJQ+bEWtBbBhabiG2ox5YVc9LLmDHIMSkgzzG+DNBOVsQ5KUqzC8uI22V7XdT5vffku33OC9OnJD8ylOi7wQ17fOPTxC7PX9EsINpUDC9yFo9tS2964GRUlUQT4/2bjI9jC0ksSqth2nygpZymarqc+klUyKwiJ8h2TjJht1mZzjQ4nPsFMIpE5siHktgMOtBSoXfFwjSJfl0kzmCsKT2H/khsj9yy+xbFzfsvG1wYi2d+otVqVV1Be3XvHZJYlNwvV5vD1a76vcMV2197tfX3D77xoGL/w5pvnrvme0qHafkL8q+/8zx7M/+8Ur0nqWssaxksKfFNuys8a+7Z1c9HXsOlbx32ejx008eePn6no3jG0dLuzYk13zz9jGTKftQtM9dWefVNR36y8l7//VrPVPvZD967IXs+69sXNbOcsH+4anvo4o1Zd1xt7N13yhqUqn7jn4NyxcMIusC/28AjFshR0mAa2WYq+EogLmSBs9AexRj2lxEZsZBD4qTXBSD8/5+sxfBVAMoY6RX7qJXruTM7HNzdc8qLMYP6VuyP1VxahWnYo+fXmM0oCeza3UCzdE/EyqdTpwJxjjhPfBHXwM6LJSHKqf25OI1K8QvBI+UQ9BS7CHkFGNywkSzrGaMbQGTkqSj0ZyZVhmdAAqCcD0YlVQQHFfAjaAVaNaDOnjwgTElFgtwKpabRBUeiOBdEnqUeGMJIneIN4kKBP3e99BjV7xwaX1p/97u515pv/LFi7NfRlN/9U7Nli+tzX4FNUzetTb86lvZv2OPV2+8dU1qz0S7yfXNv1j3lR2JVU9+tWtff9lAfNWeui/fQ+zl1Wc/YCMkLo1T6Qgep1ubszAW7bzLdVqIn6Uki1swzWgpQ7DsXN2VVwEUckY0p4cYSXrkXCiir97xOmIfHjx2cFtVsdqkKapoXn2w+/pfPDIx/sBPrlhx2faxMKtValVllbuvumfintMzk/S7TyL+r/fYK9rDEb21OFhsXXv8w6/e/+HT46COIYVSVVE1kCza9TYyEdsAMmMfAJnpKSdVl5OYgclJzMlk5nOQIA6DvHSmssjpSMmJY6J59ucTFCXe/JTzvkfzD2Rf3LbtxewD2Qn01LGf4mTET49lJ9jjk29k//j0M9k/vjE5uvqJ39137++eWE34inWoAejRUd05ajR5ahRMZoZVE/1hMWF6QpjGLKfISPpMowNrRsfkXFkuQSYnx+Sf95jJOSV92dyN9Gn2+Jq5F0fnnlhDnfNcDdUqP3fhmWqWPFONn6k9zzMhKs89ULfkgfLj7p6bwg97ZM3cdmped7aC7tRQ+6l0FdEdZkF3ZkrKqjByK8GOqjavRqKTl/zA/DAE9v4wfq6/FJ6YwDl7J1hLga3C2dmwIBm02GqWgMKJ4ZRkKSMOyuA8j97Np+JziocD2SbkFbDqgWG8evsbyPD0yO1Hd1UVagSN2tiw9Wu77/jNo2PjD//LjX2X7d5Ylf0PHY++lDh8w33rHspmX91Ov/sMEt7eZatoK680KpSV1aGJZz685/6Pjk8YPRUF6CZOk5qbCzaUWnPqJ/OdrSXybslZLpVsuUQ2PsNoCecZ1by0dWYcmos6sloBMiD2IS9nvCgfx/G48N5u5rZdu2YPs8fn1tFPnF5DvzjXKz9vDn5th+cxlHeRnHHqkWTr4dPwDzv/iXO7sMWT/3bt2Q/o78LfuiAOkiNJHZMBWkQljnAoiCoF8lkFZJnSDJ9TiKeJDqdTmZSoFEQFzqWSVY/5mFhewQcrvJZmEK3nNK5AxL3iyrHI7qb9j01GNhq4IqOGU6lV1dse2Ml8a7b+slevbuUIPX8C3vnY5ygflcrxzpbjnQF455V5h7XITwbnI7yTApgmxgs0mVLyGOXFFrIERnLmduIUUIQJI+FPO1ebixwWPb2cL7SOzt1kdpttPoF+cLTAZph7QGe2e53rwU1sZrScjh7nublLLKBbLuvccgCKh3SCjp1blpMz83vgHZv3UBKTm9dIVOZ5n2aofDpRUi0I1freTloEMYjj8zqj3A+f5cnPVVHIjdsYz9dXeAQS7OBMpAA4DtdTmCDYEdU4I4kzgOrClDx8wArIZgehEA6A+uDsZBj5QshmFd5bzgkaerlRrzRo6JRa4HrWK+b+hivgXca5Fxn2uNIwyxd5eS/H/N6gPL1G8eOColl9QQHzX+6CM5WL9duUt66iLkerBmg1E1pNAsGceP1NB7RaiI/GNCqNi2gMYlXx58iKA1nMs8y6mIObHQY6VPozDk+h4sTpNRbFf3gKzjRi237V2Q/ZXy/NRee9lF+7kIu2LOSiLf+7ueirtr2UvRes/uQkWP375l7atmf0gZPXHnvvvlWr7nvv2LUnHxil330arMTuXe9kfw8e4Pdv7wJrIDxz3wfPjI0988F99374zPj4Mx9i+kG/FfuIb7JT7Yutsh2QhM5A9FuHk8AOMgw9dlExUS97KRamnxNz0o69FCt7qWIFAQdeJ5oHBX9Cl1BnEdN9w19dmv0D4jbds7vu+9/N/oE9/i//sPHRi1vnXqYfrN1wTf/TMzKWvir7ltIDPMX5pMF8PinP0wrtQiLJMp9IwjydTySxVoeRBNs+B5BlTYkVQlprpFJL2YuDbjILP4vNFcOHe9HRMYtPn/1u211Dn8nxfW89fm0ku1fHoRUFhefnfJ73Pwfe28G6rM1prkHWXMkH7Lc5CPttqnnzYgf2O2KiXVYkzP4AViQ7aI9JKy8cCjjJbCP1EqJPyAslF+Pa8mYHhZETxRfkc/DMn1NT92xymtFHa3mHLlsllJa/Obvpvl113307+zF7/O3XRm7Z2a41uubugPiwz26aO0j/PLL6aP8DX5XtxfjZD5h3QWZN1D4q3YAlpgXbo20gK2k4p16ER1UK10qL8LVSP16Ea46KjpNSpSEjVvKSEYaSMGSkFnitdJBVMdEovKC1FJXEGnBcmDCJxTC6Ui12t47iBHG3udqPnNyU+dBEpVT5ZCmC61XmwpfxIj2vKSqr79vavPqmDdUt26+75bodzcndD00enO51agRD+fKpwcFLV5Y37yB3mi/9+v67/uH5SqMjUB5w1Exc0T2wtb0ynBi+YkPPjTubu3ujAgpGQpUrttf1buqMVCaGj4yvfezSzm0yTwIg31tAviqIkck6jyxaisGLPThYF5UnsRDTrBKzhMVsUrL4UInXHhciebzuGFBsyzI72aHx8dMiO0Q+/ztnf8+a4fOdVJJKW0luWyvbe5GL50ElmHxcUAb+W+LNuaVmhkyL3Fq5ZYmTjNDf2dV08KmdO5+8qHFn313fvfrq793ZT5cx18xeu+2b1/Usv1bcBsfXHPnB/WNj9/8A04FjIyfQwWN/z+NxUrKDxKtY2D1QEsXnYKw55wsSOWfoN45ADIT+02zQmdDvWLNxeO7ZDexxo+HMimhtslKR1gkADcBSU5Tqx/CMEPVzKh3Cz/AUB+PxOHmUxLnjcWxpsV3FsfHbH79/guTsqQgnKniR4iXGcYqFQynkOPVq4+/e30VuB3HV2QlJy58SdSdefcf3fiqf0OdE7wnJrD0lmk682lTxuyr5ugfXNvHY6Tl18HEumIe6UwwFGq7Q6kxmp8tbslAbhlp5Kn/d7Sn2lgRD5ysfk6gQYEuVzS/bp3gMJ4TmfWXMds4p8qNgSAlmS1jjVqN9Sg3L6lTofoWFK8JsvF+lY1m1Cu1lbNxQtm5DdpVaqdRkR9azxwvPjFuiLlfUonhaJwB7xy2VLmeEnIFPzTgLC51n7LLeAq8Vr5B8fnDB99N5tSqKYuNDSTT2niob8Z4aRMSap1IjWxmSCfcLtD6r38FxLHqZUbPouJLTTWZ1tGYHJ7DZpEKbbVWZ9fT/oN/Wa+ZuVBvV9ISam+ucMwMmeMDIzV2nETBNLqApTeLeqlwWlsqDEaucaALltuUySQSBUPJBXuUWMxGmk2steHf0MGdVq60celhp5tbNZXazxw2GuR2OCps97KDv0xlnn597ll6Nn38JPP9pEv+7c9gKcClZ4ZADJS6K7RdFFjmTyIsXAlTIa71Ez9w/e7HCzs3uZB4Omk2sak3AZjk9uwZ/5jQ4w1NKAT4zSjJ5ajYjqqISYsnn4cmr5jNpNcFragOJunIPMecXxuJ4sXQaLTNxP/4xZ8r+QeUJGIRT23hDCYXO/vnss/TJ/Bo7tXiNncFahmWkLi810leWCl41+6PgqazZiunaB3Sl83QZohIDdCnhT3N0KQAGAF0KPaZLgenS5Omy1yQwvJNDHO8+HlPFo87s6xkDr3yA5wJ/xnUxP2DizLcIXsvX81CkGoVYRXN0AZzll7TlBIqcOMFZlB+g9U1owzKdif1Yw7Esp/kTyxuYOH3J3K2cFr0peAS+WMi2q3lZn6nsb5nQ2QjEI3ZcayBRbAb/kFoIOQqxgo1lQrP/+COCo8cUT6KvgC/TgF8majaj1FNGXC1DQtMZ1koZFPlI1EzWbDGBYxucDv2jSb1Jzb7Cmf6o0mIfvw/84hqFHuxWkrqBShfg2eSN51Z32EzagiiSOUpryLq6htOEZ9i434IDcExi3aJVHoxwRDYmuXD9Mi8VGTN4MqbwWjNmlpASY0Kas2BDIhaZRDdMgjhenqHcqZSkYclb5Hx9Ert9kjGNotyimoCPlxSHQZS6r+ehj5+/7EjvjuWVRotOGBL3D1++sizkUXHlIxO7mmu29kU2+JK9pQ1bR3sDf/Hjm1s/bts3XK3Yc8e9ZdVl5qKh4ZrNt47O7Sy6rqy90u5u3dob76uyuyItJUirCDSPEhwknv1IwYKeWkAfVlJpDvOIiksO4IoSs6dYlRFRNLcGgau3JVqIkXQWrqTRGMhKhFRkxWiew3C6GNBDWiMwqRy0F/AYTbkYMARhedI9D358SpW4pTN94LUf1R96cs/u++uUjCNYf+e6iZvXRp55aNsTbeyP5i6d2Jmdy84eeOvO4ZGVV7p+MdbdfuTpyV+f3Lme6NfE2Y+YvQodRF1Ncl2mVACks5h0AQ4E4tIFPQY8lWQINiA5gpVcKAAoo6aK/fPFfAS7yFnWxXmD+WwVPdF8+Ln9Wx9IOVmtWhtoGG8du3l9LL7u2FDv1tagzqAucCyf2FW/+bGL2lD28InbBloSflZd6C1oPvzUjqknDzX6y/xar6c2ZF124zvA+3Gg/Rs53q+h0iY5eiK8JwPwAO81i3mP2Y5BhJqLxSRdjvcFmPesCfROJ4hGnEHEEqDUxkXLXDY7ia2iBG3TZosNJ4kFOR88Dryf2nFP3ZaES6HtfOHgaz+aJLxvuGti4qa1UXQGs36gh153OlLw6LoppEAKzH3ataa77cjTWIewDF4EGZSAf5ik0l4sBUt+EBXKzEyQ8+KMT1AxHz4YDbjiWTTmIgg+F0EYgXLW4sWTSCtIzkKsUBwuhaXwcUoMCgCtFy8kKf3eT4op6c0FERMth5/bu/rLU40Gbs6T2HLb6oGD/ZU6g6rAuXLrodTOr1/eMUk/Wjl8aNnglWvraNO+V27sbzj01B47b7no+UsavOU+LK2gbfnt3/7J8HUT1bF11xKd88Cgr2Rfg9c2Kl2IpQZwrygu2ZUwV2IYd6lVGUmHRwvBeiGpdCuAAdti6YJCrI8FToCY3hzEjC+GzcQyFCEZdoaCnucrhy9aVtzqZJBZX+6JjTb5UF/2pc1fcjPTpdeuuX6sQqeN4pxG+66Bq3pm9zFf0tJyrnogez3zM7B99dQQNYni4LexMDYpM9N28yZ1WHIpMmIiKrUCyX1RqQI0LRyDQEdajQ3fNiKjBj4jNvCSUgc2jicr3StxHoiDaB487kqBmMW1OAaCQzcvdcFhtZBJV3fhMVY7YIzbZUj4pw9OPCkvl/Tz4vITUrn6lBg5wU6HyyPm8KunzCc24SqN6Up8Cm+Z7ulfbg6n4XRRrQZcw7UaL/SXV0aW9+RQ3ov95eGFU3mxZW2pYGrVMGabX5doXb0JBy9uQSwATeprBU2qbsDBKISlOGXlB6tVCmerBUlXAq8u0zTnXrmWWATwp7nq3vkiX5vdiwtS89U/IbIEozzP2roixDFLl9YHdq+PN/LeiKdnZc2mm4Y7DlYituj+InftxhtWji0PVzdtv+7G67Y1tx55dtfUY/uSayLj165acePWVHzV3iNHa0LtVa6Wku7tbe3buwIly7a3tm3vLplaebhYaK+3RSNlfPltG3ovXR0tdvtctC60Odl7ZDRa4Oz0VERtSpU5MtLZcslEoqJvS0flQJ3X3zJWU9XgNQBANZbGGhkqtbGzpKRzQ738ulH23U+BIv0d2Ccr1ZXDovq47BWEnFewzVsmmvgEHOnoDWTrjGSwkjASDK2cH1zwBsTjCbL9F57a3P3CwVXXrApvOXbT5Nc7weJfvmZH7eSd43OH6dvuenzHxJwC25j7gaBB9gXKDDiimUpb5msBjPpM2opwms1xzsYjC9l4ZDeQLIlkn8/3fLJaHgdi93POYrPJ6+B5h9dk8jq5ss3shMnn5Dinz2Qqxq/Fp19mzsyyFH3277M35mgJ4ayuk6SbgAwtwnAdMJsGMFuMZJ80JzE/pu0aCwfzxConn/QaIMbpJ8QwpPAMzPFConQpfXEWGdRu18jQZk/j2mZ39KWltGYfrNarJ0YUV545VjvREdQqv7OEcpClCLJ8E2Tpns+lWuJpHRA8wxRROpxIZWWReggX3USkUjHJpRaB/Pj5XGrifKlUBHhY3FLFOXl0r85hXp1t1pp1vF2PfjrK2fTZVUKRO8r+aPZitRFdrzNmR7UmpdpumMvqDOg7Jm4uS/TtHfgVABoZsKwyjZigXOYaBIl/FjLX72xmf3Q6ktNT9ocEA+zLxQcOP0SnCEYny8QUl0pBY4tieRBQYcALHGIFT3I4fsP8pgCHjA6kCook1cQAdjhgJkQDKRo04RQIjr1YQz5z6SF1gTZ7bmk8p9jcOSpeW6DQuDsG1lQduMFh6li9rbb/6GjllmuP1G7pq9h86cGRO5PMGddXyrviBddd1LKuqSi25UvrsPp/7cHgwEX9+Ojuh7eOzWbzcxLGaqcGcjziciNV44lpVs2nC+3yGO1ycofLT4TcwIwCCdTM1HzykAzlE7MTk77slUMLExQovW9sz5IJKmOZ00DXObnYPAbwq85bF2z49FzsZ2xVabn0+X37nr+kpeUS/Hppy2R07c1r18rbTPBrFGWPvHVrb++tbx05cuLWnp5bTxzZ/uThlpbDT27f9hT+s6ewXXkqey/QrQcbF6DGqbSQp5uwVIOJ94Lm4ACuZB4BszYZAbtz1i6INzNSctLMLUgagVRO4FUrvUUpozCBRCrnQGEnOgcIP1VrEJAG8NfrP2w48OTUznuT9XetxQDs6Ye3PdmavZfdqjM+tG4qOytj4b6+rJHuHlsug+FdG/BYxmEs34CxYDw5LuNJAibxNF9AlNxSRMlhIF8AiNKQQ5TcPKI0yFpyXkSZJOGmcCFEueuBpAYVJbZ0Tu/PI8rkl9cuIMqhgUOu0w/RRRM75xFlwaoegihzc5r+PYzFga29nBmfl4hFlwEbyhefiMo10k4yGpi6JEDDJstIVhfs86sLMusXMpNYs+MCj9TVTxyJrPBzjKC0+6qLL747wpzhTO9dcbvZ3MEjjVZ9101zu/JrYwwL+t1I/ZBK15N1WyUEjvUkcFRowulCTFkIroUIxAv5cMjRFBXtYG0AH1XIfK4VMlKzDIren3zHIoMiMy8KJ6So85RYfQJOpk1mAXBQlJ+uilYDDoLfi3AQ3CQ4SDCZo1XVORx0zhlBQRU4L61UgAw5YVpTGMA1JWKtSfL4sHKGNDiNa/fU5tK4i9brzsnj+j+Zx13rYPU6Q2nz+q62LW2+6qFtU9uGqqNrrlyx/ktNNpVRV1I/2pRc1xqAO3vgTtXaG0anHpjyqTXeoDfQPBKJd0S93lDDaGtisr+yNukD9+Qqru0OVbVWFntLG1c3dRxaVd1JeF579gP6QXYT5aMOydG7HNIVkJDOpgnjLUieuKQmsDut1uXr80nG3k08r6iKpfVufEOPN6G4Sd7EjQvo9bzEcBmcksAugMHLyTRwRifki9Vqk2Q7KVnoztkeHGFgh1eL0yy133Aigz6CWrMnrMG4u6Q25ODVBaEjbTsu/rLOyDwb1KO9Gi57ec/cQHljyGxzWbXhcM2hI/TLBhjb7aBP32DOyHbcgPUbJ9YkZc70iNp43o6D18NJZA1ojTFG7A224xqG1LiIelyvRUlImfPRJKssT8aFiC9C37712I1bv961JVGENN2vHBq9elUYHaBvmzt81xPbJ+jsLFtwz9huMOpULt/HfA9oM+Gcsonk+1Au35fPEFGmCyb4/K5+zqRAQ1ody+o0aJg16Xuzw6uZM0bt7M8c5TZbhY0J6DhAUvhZdvDd/wAIr5z6M5Uux/6sME4eJ3EFOK8cjuLyGDxf3tG+f2w+r8ySvLLCcIqFQ6nccOrVt3/4u5Q8nXy86DkhCcpTouXEq43Z9x+S88eF8GcOXizkJTve6OyAUFp96tV3yt8vJiXiAsw7wQLzzsdPF/s85vC0F/9Ow8VFsw/uwIvoTVGtOgUrmCx2h6fY64sszjwbqdydgkJPcfk5N/PTExhYjtdo/amlLASjGsuv1+LKa7wgKiff8KKtvZczMwipNApWr0YmlbXUrkIGo1ahUSNaXbA8+9xyXpX9LatmGDWb/XeluXOB7WE7E7bbZ9+NhG0VdibgnGVtTIPRY4T/Z//GllszYW4DuRfM5575eJpGueWEwihO+eRzz9bFuefEeVLPAXQg+/B6nHoOKzhkZ3ntRPZBdGg9zjx/l9Vm31PxOlqD/qDXZIcEC7pVY8ia5/4gaNDbFmN2o8aIdQP82feBHhvBg7IKitboQqEXZb2gFpJ93vYhI2jiGqVWweqUaIQ16/rmXlRaTMtmCFt+aywW+GKecei4029wJnQnPKMfeLACnrko15xPhZEqzwvkmvuN9DVzX6F/aZw7Rh8KCVZm80CZTZj9ywHM17bsH9AZpUAtR4cosT4q1bAZUjwKIbgtKvG5DS4tELu0gheO8hmpMBKLpVuipIARacLTndEWCGZUHfG4VA63PWG4XU72zJSnwJYJMbzrhWyYeOOjdfJW8NaIGAZd46WI5pQY5qUOzalX31r1kYZMIW1E9ETw9uNCuOnhJRW+WfxHA5kJWn5arVXBBNDg3zBhposK8Xxw49+vNs/+8XHytgg/XREJw/VK/BueNN3W2gGn7fh3Go4Xpo3YnkrDu/BRRSoNn7boljuVhufgI0AarbxKrdEWFrk9eO9/a1t7x9JVG/SSWlPkrqic36uen081oJXleG8PBCIlKdFmknTFZHbV5kAj9moNiKTuc8m9RbXx+BQv+BTN11jiP2kLNJTbzHZzqGeqs86k9lUsr3Gb7CZnebLInSh3wqG7ZnmFT22q65zqCcEbbeWN9JYWW3nKW7dnz5765j0rKsI6vSc1HKvfP7UnGWyJFquUxVXNwcTU3n31seGUR68LVwzubknB2+t8deV4HiJ99l40DvrCyFXG8yGQMUN+5BAIgX1H+oHsvaqjf75JxkxT2T/QJUTPrqPE5fLaQV1USoKe+aNSKKdnEJJqC0HP2kGRIm2gSO1ky2V7HehZU7tGTZpfYD03OEHdmuBd1c3wLq6JbNFaDuoWXFC3b390j6xuzogIonDyUjVoVIQo1qtvRT/6K6JuhojYFsHldc1ws42XtPim4Y8XET0y8NM6gxYUR49/v9r84R93k+tOftrlLITrBfi3WM1PR6sjcFqFf7/6VtlHPydva+anW5rb4Hor/p2GP1mkXAWpNLwdH0VTaXjbolutqbQe7/tNiTqsd1qd3uB0FRRGAEY1t7S2fVLvdHpXQbSqpfVcvasDPyxx7aB3SQH7Y79JclSmUrnlmEWql9uTgU9BAYNN89tpSP7Sukglw2iK1/gqemrcZpvZWZ5wY12DQ3dNT4VPw9d17ukNWWwWe3l9IFBfbofDUO9UR92vZUVL7d8LitZcVaxUFUdbSxJTU/sa8oq2Yk9zamrP7hRWNNBSUDhQu1TznsEKoj93odcVFnoOrO1qCuyspFVn0layNdeKEZMrKrFwhXWRBXNeM9/rxWMktUg4zOSNci2S0YNDCCvGmi4t9nSOxTEdAZrxXGBHNtjd5W0eT9Xu272tItgcdgwWN0+kavbt2VYRagw7EHq9bvPystLq0oLqztK6zd34sBAOSS8amCvHAZdzVCHY7jSDDbVenwFvhVdLyTqeNYN/pgvUOCFUaMD3REucZGStMRLEFRQCiXoGU6uHQ9Ei733CpC6kZJJxMBWC//1E6aIuNPNNaDYyz5cmOJevFO7VzS2b7z8TmZN75jyenWPOKLJUlKqnbpL3UoglcakWAjJ7LF1LKh5rCzVynIZXARIqnDAmpfwwiCogtkpuVhAE1FpbfFIQw3HJDsdBXlLK1eliAudnbXCgi5HK/mCCRPeSHaPDEhhdohZwP0cJxfNrHov6dXCI9Osg6QycSs+37GCSuZYdj7dd9fJhHTJyJfrxWxMOVmPy1Q2nKgZ2dpXq1GqF07FsYk+DfH/LXx5u2VS19pqhyg1fnqxB2Yv+6tZB+kcGy5/UDVEfq3a4C9jZa2l/qVfBFrtjQTv9Hm7F0X/Da5dOPnKoTcVcybRe/ATWyS6KUkyxLwPXLpI7PkiVTEY+ADea1uHcm0uTmaEUcZ0hLBbH8eqiWCIzLnUSR4QhvC8olg6l8nFZOhXChykKF7am4powZhYlVeIOJ+UpyaUAbeDNsvMgi6r5Dg+Li0oFeY+fQLbjx+UTvGVU6DILxxO7Htm54tLxVltIYxA4S7RlrHno0uEy9B+CIVvT22oPO5ig0zrr8bfHi+ibvEYrqtz4xJHOYNtYtZ0VipuiBbUbb1yZ/XGpzpT99torKhSKMmNRh6GsYagWrZD1CVEQNm+ASD9JraAwIiqDMCgOU1Qpr1wWn5QCoAkBnuSzOC5DFivxFqiXaLVgcRX5daROK14GV9Q6coWW1SJpl6PlpJ1UmytVdlVIbuqgCpFceCKpWpKNeTz2cORAW8uByMOxh0rC5SUPxx+OHGyB80diD5eUl5WwFX3bU6ntfRX5V0V5/GF4Y+Ch+EO5P4yTNz6cP/95altvRUXvNnh3f0VF/3bQhTWgC+3scaqYuliuTMvXusy4ChyUvJUUr2tYYzNuD7lgjEtuuCCAOnhxuRPePYXzYqZY2u7AOmC3gmHjY2mHHZ85XHgvcUzy4USZg1TNALLwLJTPEIyZT4B6reQ/XJBbS/5bs7LAgLaoOVYjoC24nCa7Ak1mb0GXZm/ZLL/A5eOuuTWWgOAL0cd1xtnvNx5pzB5FN8ELqUtb5PtVME7i/dVk+5cihp2/qIxJKrCxmnkMwMg4YACQAFMw+2+K9Uzh7G/kGrc7z17GXEP2Wq+jHqHkuWJTZtI2EinbBBhsNCo1wJUGAjUbEtimrycGp4fPTCt7sMUsADTQw+NeQ1IALpYHRuBiK1xsjWIwipsrbMg3VYilxB5BTIDjNYl14GOFVr3OzHhC0YauwaHxCZyDGDGRMjlbg2B6QcmVx4YmcrYosWiZZWnmQTm/4zoYSp6brADjpAB9lRdd0J0bdtV1L8pGBBpGm1Ib2gLxVXv271kVX70q2UUyEg822VmDzhBq3bCsZWuHv3bswMX7xxJrSrsmtmyP9LSUNI+s21Sxtp/+58GrgsFt/cmtA5WJhN/g9LiKE8tLo8vqotWp7k0to1cFQpPdJGNR51ervcFiX/NIVc2KxupYbffavvL2RCRc4fJuaY4sT1WWl9pDm7FcShU/pKPsEYivS6gaCu9O8sXJhj9HDL9IjC0GChuMiogsZ2CcbiGL7Bm8WgpyN52bG0WBJeelBkcRRDZ2jrMX87zbgVYaHO75C4LbwZp8HnziEXi33WCwF517Ctq35uwflEVgdwvAY63DPY9IjZtXkUmrcFFGWEEFFOGZsX6ryhCWxkCF+sewCvWvxCjSqlKHZ2rbyb1abI+ITs0UytupCuXtVN1CRuzmcfJ0hpO7n2A1CnaDObJ6VeHa+tExYqCa+gXTi1xhsIrqHsUK1C6I9bLzUuDiQ7wZDW8xWZofti822osX9BO5rf5yYmRN7aabnnh9+/Y3nrxpYyKx8aYnX9+x7Y0nbtpU27j75Y/vuOPUK7t3v/LnO+/4+OXdH3Rd/uy22vH+do9DxWl9DeuXjd42mUhsvn5wzVVJvY7V0MWNT16y5anD7fS7297EH4E/+s1t29/IH7+x/c5Tr+7e/eqpO+889dqePa+dumP7s5d18kXlhT5dgacgse2u8XVf2lpTDngaPmt5x9Fn5Xm8lxmmO0AWQdCWq6m0Bc9jjWJx2Yroi85UEJGIsegMS47ymytC4AVCcqMpFuN+B7gCvK0ihON4TgDkWi3AR/nwqqjDJBblNoFLToBsYkyQqKLFFSzm81Sw2HAByyfbG9VyaG944z1Ty/oqGssKdUaVoXpv1449Xp2O1bpiiZaArzlauMziDTt8qViF7esPML8raY8V0zUrVtqdds5eHbl0W/Zqtb7LEXAaTMGGisJSl87o9FvuZJcRvjxC3UJ/h3mYzKMglZsxMy4rpQY+FMdIaYEL4aJks6Mo10in1my32S0qBm/+NMORES25hBd4H/nYzSP1awaNVv+aCgluDp+rXsfnr6sEN23g0DFea9Trsz+xaNWW7I91BqOWR9ef97Icmz2D1jKn6J9QLFWV3zma746j0Mh7BBSkm1JaQfqMKKj5PQK4A45feIZZuYq+pS97E4qAGzxnfi6jBqknLzBDu7rJLOwCrNTVjT+4qwrUpTE2Uz1IblSz+e3sS6bnMjDt3TFxGS/14bw1nNWeM1lXwtW+ZWDErd6wqo3sHa0VIKoSgyaxEXSou0swzcC0pcitQUGs/RyTlhTVyeZ+SbV0AnQujD7/bEVfnXvo0euP6C0aFBjWGpXZ/6l2FRy894qj+44+9bnn59zzzG2XHN1+TFCZjdmbVFq0Q8dl96MfTa7fsBpkamFpmJddC31+2IxcQLjQ50d9Tp8fC5h9uoPsJV7PjNF/y75K1svaqfn2cXhvNel4klst4xZWy7j/ndWy9VUjB1vbDo5UwWtb24GRqp6SltXV1WuaS0qaV8eqV7eUKG5pOTASjY7sxx3d4G37W/BV8q7VbSUlbatlW3SAGlZUKx6CMRupjYv2QOOQBaCnqImlFaTmSsHhYEZBYkUV1nA+KnInMX4xGHE/krSBw/cMDKijNpbmDCS9gONMQDqCvLtd3ki90P6JeWu2Jd8Carivj97Uhx7NburLbkMP4Dm2lbmf7lFeRVVSvYSyMuCnJSpq45irBQp5x7r2pFTMZdLa4vk+U1EM/stI15wgmDyLIClZ3D0HV7zLIUDLfOMcucfbfOEeaWxI+uYUoa1KzQdFsaDNUVpb1NJrVVloA+Pmrt5YOdTgdYbr3T8xl1qR08nc71ALqo+KUvVN3kCt39STMiPEbtlVEOurLlvW1uh5j2UdYWIzJpm/oPtgPC3USgrCGckAUNYenXHIhr4EMH4Ub2pGgMRE00mxICYlABpWgaK05TeGpClFghh2QYynpOISGGRBldzwhlhuD3IzizreoPlRqhaqExehrwg96VGoWLWRYRSWksZIeWuZzRbtS65fZy+tcbf1mpRmFe/krlpfuSJV3NPcNxhsH6tuGkl5FSsMNK1Wq/XlJUUFFbVOX23QGqMHWv1xH9/eaEGMYssuV1VnRee4RVjdWT1Y5/HUdGEe/ETxJC3k60EVuXrVC9aDknZ7uEr1J4/pnI5NP1cLBsWTfzRx2TmtSrbDt+M1UuYMVYRXSM1yTQvIe37VRSwAxO0mk88lkLIW1zlrLx7sU+T+YaKGZHz0pvkVGIm3pS60BhMMAROxn1y8FLP8Gzsnbw6yTLXFkX2HrVu8HDOxYbCnYqIkK9kI3cmzTYpfQexjxrU4xFroNfLqFplteo6UAiOs7xzpqCca+BlKdoVUFOfecLsoDZ+RrPOd9iBq9ZPthH4Bm4yWi5/ZTf/bv6/JimO7jl/comgbvmFDfNWp3yodp37L3JWavAXTcRz9GR2hvwV0RDBynWH1lAXcjPxCHg9C0VrJRfll8QMXWajjfGGJxRYqFITCkM1SUsjTG+bPgoU8D54DP++m7N3op+A1i6ijFMhmRk2UP60mi4Bq0k0OpCWcnDHJ3ssk9+/F7W89ub36sd91yjlKIcKJ/AmFZHKd4kTzCWqaF0xmktyDcD+/VV/A2aoCbF7VBaQlUq45FIGOpGNpMr4QjdykVWlZobDMXVPvirWXhpvdazcWxrrKyoeyf1Wk1xl0lSGX12Zgb9nCNzd6qn1mB4zpPrBTHcqjYEF7KHD8Myp5QjO4AzMelgrl7KWaJH0v0IRMWNSEDNMYF+JWb21cSOLJG7rvpw33ZK/4S8VX1Gqdmn39jbmRWIwuC16rRFpix8eZQfoJ9iWQo2fe/xQpiP+x5woXF/qVuuR+pSSz51rwP0X2T/E/NtlngzEZLx2YWtY51V9a2j/VuWxqoHTFnn27p6Z279ujONZ9cGU4vPJgd/718PXXH774hhtkXzMD+O6XgO8sVBkgPCSWk0BYG5sJyo41jOMFmItpJW9NkWqqZA1etMUdNZhgbU0LMluZULBk0cVQ/uKM6nUlXqBUvq4yuT/+2C0ghfo1+QpAPvnStE6PKnUGBcvpUIXOwGv47JVc9gpeI1zoBqZbQcFEYb/MPg/ydVKl4I0el3fmiP7czkhLXAryuHxB9MZnymThF8XSZUEs27JCTXhGpeSRIbygGMRzfZo24BXiAOh7eWzGn4NxMdKJJachYkBIuwrKsCvwk/1HUlmQtNzGu3YrU0v0BzfzyC+j+UsQvmMJI6u/1usjjcCSt/y08WvZK7F2aXSqx5i41mUJz35XV2hCZ9CuzmuFA63ZaQfdjkoYxYevz6ue5kyUvUEwn77UxJ1Cv856S/hvfYsvQWscRXLNKubbVI5v3dRjVNolr0FKHWwmz7mZsloX3phXBji3rJYwLEIY5lrCsOWfi2FSPbwhQKo4Ai6YVD3nsGzaGqttJUFohwu3WmoF9pUJaU+sPtc07kI88y4FDaoLgIZzGHmAqdE6rTIj6QGl+kOAE1Y7hhN9FqWVttIO7hqAE/U+gBOen5jLLMjlvAB/nWqeYIxmjDGE9hYzomnFlp0uDDK6W5sAZCidYayro0RX01Qb1UdNAKJ7jUq3Y66PxtOVmOPL4lKxIiONtRN9HYnPrJVZPBhLryUR/9oVwH5DU3slCAUAyozDjg9zIAWJm6JiwUmRj0kx3IwG56fr4CDGS6tBW9fFZkZlbV0RkzYD61fXwWzuH1iL9XRUELuB82vHQBr9KbFJEDem8pimLodpalNisSldUh5LfS5MU46X0s+Haj5d20fnMY+5pClS3lIOmKc/sX6tDTBPS79ZBbZDazIS1FPn7W3qW1GCUc+qOl9mYWYI6A9LZgZzXQ4SlQWLCsO1LoBEFoBEbf64V+hJWEBgzJZdzmqMiczCmo7qwZTbXds5+/iFphBIK3s7/Y8KHVjLBmoTlY7itZCUPgNIUbLjbfKNS3dja7jMtF1dzoWlGmtGaoIr5bgnP2sE7qoFXM6mMU3bS6IpMgdSdlw0pC4szpVHNytaUNyOQ7mFEnxbvgb/3E7TwXB1z+r+GlrXoYQD0gOopntze4lWo1G4SJ+g7qs31SEf5/JZFlZX2lbsG6yPJ/xPf4MNNyUS3Rs7kmONxYGKgEpZWhgvdZQPHlLUfqIfECP3i1FZSL+Y4k/tGOON4lzvZ3eMQfMbjT6td0z2Py922rn/6NEL2vO3kaHDGsOPFer/OzQyBPyycOnTaBzLcE7HRdl3tSb9+WlE7T82aH6uYvM0Kj8mNIY+lUZ59+fn4GMybifxE5zi5aVPJTU7++G6D/vUFtVxWkGrnlWZ1Rei+HvfY9kbYMKwN7ALdP+C0B2jDl6Qbgwo7HHJC2FiNCoVwksgRjrb2E/OxGS7FCNeYqZEznnglnKBmGB6AZnoQnM5mRW5IUtRL8wcD1n6vZCA5lc/E8mFxU/lp7Yj+jdzScLnb07VFoYrUdLkT/h9TfWJwnAFfQFeDPibI05vibeuItAYcXmD3vowwSQyT+YIT8qpRmrswlwJRnGfw0IwHJFYvoTRa82IXp4grriVlDBKYRjwNG1C5sVsuLDklwDEEnl5NX/6qXrwkcHu5nk5Q83jDDV6ttrHux0Gg8PNC3B+AV6c4D34PfhvbAaDzc37YovOqAW+qEpzfEl8mrYEozMR2fnVRGcKc/4tSbQlLGtLmKRZZ7yytuAvcKjGTb2ASYXBc9gk1URAW7z2z6Et50PUn8atLxVGmv3+lkhhYaTFD8pQmGivibe3x2vaL8ClB/2NYacz3OgPNIQdjnBDAL8bfggGP/s7ilL+hvTetFNfodL63P7AxU2LREtshjPpkbwAx6lwl4oZVq2fb2TkiOKSRRyLnbj24zOkIsQSETURHFooCk6JGl7Sw4uCn2YVGnN4Wo1/w81pgwV/+YgZ/2ZeUrBqjd5gtpz79R9+vAxnzv0AC5VwAfioMjPFzHuzb/bSR+a+MkA/Oqepn3s4Y3CjFrpySm3RzXdHQm9lx100x/QVRO2kd1H2btL3apC6lEr34dFG4ue0LwKJz7TLQWg7aUDc3oSjtaHFjYzwTqiYkXT7lLqceDuShXVHosn63j6iBe1J0IL6lNgniLHUf6t31sImpGBoSXQaoT9/U60dV9y9xp6PWAvOjWVLbs88te6zu21F+5NuNJCPbs2Lg95L1AfeQmoq34dL0QD+TkdZP7vzle2zOl/ZP9H5asFDL+qBNVe+yCHnBK6y5Hzw/wOa5j3yYpp+s9gD54hShnNOd4FX4Hd1VOFn01X0WXS5z0PXEi+8mLy6TzrdeSKX+FmZzjmg00NVUzs+nVLcNaoyLgngVvzgVmIXJJuYA5zCAZdj4/EWJKnUSha+458cyad7lcXjin62E8mP8/hn+g2awl/s8DjojgY8RxGV1uJqBB3p9sSRHLPBnMn3C5jXTLxUr5rXyMSunCqe+jZpwUVTb8EHr/t8nzmvWfgz31rQKP2uvCqdejfX2IsG7aboEdAnnmRSyB6XtIl8rhWnziRLrn2DRcBfg4F0ci7FvFRLcFrTulQ7Htx1rlrMPxb0Q4/HA/qB9+yV4V5WZNce+dIjYxRXP+E174JYLrGzeKkb99qx86RDeTHAjfB5M4iYHvO5AtcvFfKHu4bOlfInhHtqByZYefw8Mo4BNvhxrrfKjtyeJgG0myHJMtBuRBkZuegIAXh0w0h8UdFI9vsKZrzfLC0YyWaFYk04bRTwoRGvcAg82SGpsWRwz7tcMyyNXa44OqfZoFcwL7QbxEof+zktPDD30uTkS9n7536/Gz197D3cdPC9Y9lx9HB2C/1GO/3sQu9B+o25e/PtB+eea8/1Q6wFbGyiItQVn+jYhbEf+PAiGE04KjlYuS17dHHcaAaAE5HhToTMzhzcwfAw3+ELrx8WY4TjCKZSi3p9SeEivABRdoGuX+YLAOQl3cBOfQom/kSfMGXifICYkXuHwVzD62/V2Mqep3tY7Hzdw+K5NbhpI1taSbz5F2wgtuCpPruVGCqcNxefq6sY87Ts3P6/jm/eNn2O8Z1cMF2fa4D0m/OOMjdGsGt4jHUXGGPqfGOsXzTG8H9vjEts4+cYavlS0/k5B3yO01007l+QcXdQx84zblz8WBqXYiyp0qrE7Y5hHncu5kUpzNwOeeZ28FItnCXks8QCnzCOre2ACMbo9FeyDedySmqFSFiqav7cPLvA7P4crOu54Iz/fDz89vlsgCLHxznCxwZqgNp9Pk5CgNcTlyrBU7UAC1csYaEUs5JsJq627YTDzgXm4a9za4xhJXP62f+Wkn06uPkcfPN+Fub5fEal8TPxEKIeok4rGMUGwIKUWYOSGmTXIJUGPYSuyt6UQEfRpYnszejKmux12WtRFF2NjiazN6Ijyewt2WO16MrstbJe383+mn0fvG0llaI2UGkblkZ1XhpleD7Xy60+QQA+npQxCcDqBnj14UVZd0pMCC+pWZuT8wQjuPBEwFu3KamsWjC9RHGC06MuSeXDrFyVKymAtuUFEQypyN6hII647Uje0Wqe36orG+0r3h09pDdZ647vOIS5f8l3R240+ITKN/Yf3bN5DT3b89JezP//2f3N7VgeY0M5Pne23ccbf7Ml++sZwuzm+hmBp85uQSWvPXFmlYKtbwZuz/XUJDDzH/xoFcYgpM8c2HEn5cddWT/ZaS5wvk5zJblOc2mry5NDc+ftNreATc/Td+7jBd9zoQ507FbZ3/zfpnPBp5yHTiQtciIXolRxWd5x5GgFv+Gkys9Pa/h8tFYs0Fr06bQu8Q3nI1n5CWdwYcKXOAAmR/8c0F9JtVDrPjkCsSwqNsQlDxit6hgpD1kYDl7LDVjnC8MTcJhYGGRbrkZcsqo/TW0+3TKdZ8Bzn2mJLjj+P3+G9aHl/nSgexbK/ckOdZ75DnXFn79D3UIu/fy96poXx/Dna1vHvDuPUxb6vHIgsb5FfV5nDEYSHRs0mRnGKbcz1sx3JOeAZNoYi4kcj0soSCdouS25cb4t+QVavu5E3Pl7vmZ/Lnd9zf4zOkq6vk5j2/29sx8o2tjXqF7q8hx1xZTcuQkgg6TEBbx9hKReQ0bslb+Zlnyjs1xVWiBkpnUF1eqw1AIhQkuUhAD4K2rr8HeVlvlT+Ks0JWUnvLYAlLAVV9Q2En/YWYG/eajAH5K/oWzRt5coFm04X1LwrVj8rRNW4XsdR57esubmddGqnlU9Vb667r5lKV/NumsHd3y1ycZyOkOweW1r48Y2b+PEronG6r7VfdVFrbv6eq7enFSgHU8eaqwZ2R5v2diTqmsMlsRK3L7y5tHGZRevinTW5fast6yq6hquDcX722K9LY1do/XFvW3hiok7Ns0imIukxxz57qAk1UbdfZ4uc3X462E/q9Vc+2e2mus4p9XcDGfx1zVhB3ehZnNSHQBcsekLN51bcAlfuP3cjvkmfF+sEZ3i5lzLvs/Fz8b/T/xsxPys++L8nK9J+8L8/PV8EdsX4ydzcb7kLc/P44Sfy6kHzsPP1OfhZ89n8rP3HH6+gPlZ3zbPUNEliA3nZWvqv8tW7GWj+Ct0EfGyX5i7Vf+y5hftvP5RJUsr6cdYTvMFmXzF7Kz+aYVaoaSfZlWLdPdWwusR6t0v3HESW9m6uNQOdncoKjXBhS7w3qsWsx5M78yIHKeNLBbE9DJXTB2e6ZJvdUVnlslHC/IZXSSfOkHkUlLXCER2Fn9lkwavSkhFMeFCqj/UDldaV6S+uJQuEPN9YWElLKE6n78pUVNQUYkazcGk39dYV1MQrqS/oNSeLWmLunwhX11VSWu0wFfqa4iQdUBZdkeI7Hqp9dTbX1x63VFxIi41AegaArFtWCw2vPWuHZBW+zkyG8Uyk/rhej/Ix7p4Nm1cJK0UlpbYbpIqsSvtFySLBu/MMElDE3KZzP+RZqOftafoC4ss+VmbkL6g5H716VuW5mX4cyLDPmrNeWfgKMZdTfL63afLc2awm2syhGcGcyu9Y0vnYb88xfp5aRjO2uWz9guYx/Gl00/sN4n+lDgszFgqm7o1nzEDRwfhSnvdf38Gnm8Z+QuL9NbCqtZAoLWqqEh+LWzIry1/QYevKGmucDormktKGiudzsrGknhbW37NmdhRpVGhp9qpYZiJIpVuxlJMxKXlMMvKYqTdn1gQJ4vy47G0xjovvZFAs9UQFlfEpREF7gaVn4YdIIsOXhqQJRMAmDoSwxEQ/tL3Yj5DplsHRb4yRBwQ0py1GReYBUySA7+uEtIFZaSMvtgkRapxSjuwHNdCwTHZ0iiIxbhUSjLN73JfEFCu7s9mn68783uXdCzFXwO/WG5NcBXle5guFpLOyAqDz+299m571Ss3DtywpU7Lza2rnrh6Rc/2ZSEtp3Y6+tbtrL3x7SrLmv3/q7dzD46quuP4fe4z+7jZZ7J5bTbJ5r3Ze5MseUMChIQkBBLAPARDERGCgBgEX4hCK0lFKyhi29FSFehUu3fJjNba6YBV207/cqa0U1un49ROM+NMy1inLUjo+Z1z95l9JNX2D2DvJsy9v98595zfOef3+3wfWoaaxLeluG1YXHn/iATNx5xgtlf07GzvPTgs0prOAyMBrvvJFyrESr0GNdmxe+99vO3g6/c6zAdem2pxlxfrCgF++uQ3102uzC9cuWtd03opp2bzkfXH+YquMdqweXqr1HjHCWDwzp/GDN5u6igV6oK2KpNklyophjfo8802k9evGRedNjfA8fmaMJsXjvxwIpppDidjttnh+FzgXWVen9jZhdcNzT5SatolQLn20ji+dLqTczYj4Lf2h5M5Y3fkiasrKgdzdSodn51XkV/f4vJ3lpeOnNrVlIb72zLIrU96TH5Y1X/8J9DvMUcXxb7A0cX17hGSrp8JE9wScbotKXC6rQpOd5a3uv2g1pAGqCv7YZRpXAJYN7pIWBJidyayQFgUbJflo+uC1L5p+N/6pgF841+Cb+hIwL8k39DqSLS/KOfQ12LqWsL+uYj9syLOP2JK/3Sm8E9XrH/qM/hHXKp/FkTuS3LTcGLUvjhn/Ts+WOcUfx3C/uqiNlHT6bnVsIc2JMmNKLjrQbPK5gTPAby6xYZxyXBmMoA+DkT9eRukAbWgUcrqroaTAFnnhfraL0u3zhSxLcmvY5mitUX5mdmSPkhjKBSI0VtwPZeBqlRyHGCvDkMqI4kOBpLoIFN6BU8an0ThiYwj7RMK7/9GL4bzKnXBFP2HhHtwKe/B6SNlPuEXF+7xYuR1tE9EashujJG7MLc+hRvh3AAr1ajkVMCeXiibjkmsMMQlVmix3iedrdyPTXwR8GZrYv8+NcG9Ftt5bwwphrK3PkN2XsccATvJr8A7n1aa5FeUkfyKPJJfEUUJgHiUMtFCfoU7kl/BJPQfeJzEPmZI6CbvTNRkQAvc0MPzJn6L22ns1j/Yv/MvIv/1ArtHhPevVY21sjFrjWw6BtCzBsywMw0KwzXK3uKKAFq86vnc0nIRxwSgjB2ianRx2s6OWtqLtYU7YDMek0s6YKs34MBl3gtlsQME7jLWuv/VXY17dtzmNj29/4KgzjradmKtTkBNMj47+B0Lb7xvxe51VS33yVO3f/+B1RNNE492j57YIrGm1tHDA6NPjNfSH2x7/bG1ec2jbT/+V9/pfI1Ol7W3uM7MmIysnbMa28SZAo1Gb9hR9/C59w89+ZdXRjofkvdufW5H4+pjP7u/fucGqW3PM6QvEwb3NOWgJOpkCuIvnFc4JblYNRes8+HkDeDf1CdQgFFjz0pkkSKZ4eQlRt42TAhuiBKC5VIJ4qp8CzkgV0DBch2gAYpqm1Ijg1Ot+ReihL0pF/XJIMPch0mX7mjuw+xhRQfOTw3H0IfLI3MfRhCLyRDEaRIe5HKY3GoWUV8dHZ8yc4m/HRm9MhKK2U0kAkpnY/WXtLEabCxfhI3RwGYR7GVHZPjMaCTTGYlkwnZeVHI6Yu2siLezKZmdaRI75IrF2rkgQMls7vbEUTuz0b0J24cR26cT8zpiKNrhvA5VsrwOw+LyOgxLyuvI4KoU73pmj+1K+e5ndt2hFHt4xH+HsP+aY/M5Yj0Y8AV7ST7H8mg+B3FdRXw+xyr0cVXUaRnyOdI7KlOsltlhuzMFaJn99qMMO2jQB/dRH3N+DjTuLShWq6VAz0CdNRcGPbh9siNrDp/mc1eDVlHOskGIAdOJwrigY8+Cy4S4q33s5ZuXY/l5sZ+ZE2vXzr9ZvsycU2KxenJMAZaOuSDvxyXOwHXgeqlGaqOSH+ILbzSUw0FlANcI54uy24ArVqBkR0CtB2eW9W5AnfF2p7GglIyC5T6SFuIs0JQ0xu0fBBQsnqL0oSYoPDo2J8ROGpiM+KOnlo3orRbp6bbl0ISv3DNk8Aje6dXdW+tEhqs93D82vcX31Mj02PTtvg2kqcTa+03Gy6uuHIb2Wr9PML+16leP7brQwrxRVbvi4Pl5d/fyqVd3/HwKxwGYF43GfwflhhP/eGK0k1H46BgbXZwCG+1RsNEhixMSGBLQ0VBOmZ8aIB2d4JKgpN+NzmjJoNLcufA6PoMdeV+FHXkC4XcntyM6iSVDYq+IzlrJDGFPxqy5w7aAhmj5Qlty4mypSGFLZdQWVxJbctLasmCiSmLSyQUzU1LDnoufjVjFtkPItkqqDXh7SRnlQa8v2CzJ+WiAqBOxpGjUSqCUF9twnhakzjTYMEEoxnbQGsWkKYsKzTogirIolHmmoTSJE57NOHYmdcqNjOMlQxjVqD9DFSdaa7qYKC0do6rD1ZsKqjroEoKO1MBqNtI7U6OrhUgfTQ6x5o5EO6mib8F/gFnuir4biNoSonUBlrbAKivkZcsGfTeLKEJqh0vRd4PXzZUd0XcrsMfou1kS9d0SRS0mVob2pRC0UDffPDh6d1jbbbB/XhOvZ8Eqvj2EV7et1EAsAxwS1ZtIkaKPFCk644oU65UiRbeiQlwlyBo7PH4mZDiToXelbpefZupkKZrr0wy9DHSuP9PcjfpYEVVPPaEojtkkuYydC1pEgnU0hivU6ti5WVN2HmxbmaA8iDDg3FbsGUDA2KtEEdZ6wMA0YrivERiYWSL6IGircE6lDmpZebw/lQ2YCAfoxYQodxUMUcZsZZeKZLAyjph6HLeA96iSyDmPvfznma3nZ/aUsSPhkpwvzpftmTm/dfqTl8d2989cmTp4ebqvb/rywakrM/1KwqR//NgwvTFcqrdp+NhY3c4rtPnC2WvnR0bOXzv7/LWLo6MXr5HYWfUIp6dEajXq56epUC14CcXKy9RQY0KwugZJ7kSX/eJst70WXNQN26AbsIsk5BKJnD3A7ki3CBskayDTyTyH4ZdtaD0s1wIZyo46E3JFcE12yOAqbyL5TUWg5yTbl6GomiryVEk4maQbJIOCnUqPU0ILRSko+UEQnSx65MNbfiMt+87deer9KuuaOx7o7f/615bpTTdv948dGVh15+pKfZbG5ewbv6tx+r3aql88v/2lfS3bKzce2Tj8yHBlJfoLfaxkVcydFWt3tvdODYskCvnuzMrJgcqYg5/wtt7zz518KUkUaQmf+7Ak7051k7Ki+a+ZGorPvIMQsVGSc9EbWk1ovLarcqENk6ItOBMPJ5BBzO23kT35xSbnpc8+TJ6xt4ga4mR5fNzQInKf3dxrTAPeC6yJaqoKCodEwEQkBQWXHVFX1TaFK6xi5m934mQdv/UH9/Jyv2MCaI3oovqooMUHtbg6FJc7fTgFwSCCTgPc0EUWfS6c2hlm9oFkp8EF77YFOqsTk7nt8WTu+IVc6i2apNsxNLWDaWS6GOgdFKwGdtB/ZBqHhoif/tufnWGq2beZKaIhSxYi8CdGQxb+yxm2lKnu6SG/z7+f+ff5OuX3j3PNdAP/OerHzVQw2zfLZlE6jmziooFBb5oL6XGBoh64MZR51mSlJORN2NnVk0NjigBsYVtRDaKAZH+xlj4+0J6nUXmlEt603G7lfjN4qs2i0qhV9XcFWjs0WqPK5e0nNu7namk3/1f0DG34GbKiz8BflU2muaDJPKvFNw5qfSEtrivTAr4OHsMEextZ5DECQDwhm56E3uwt208eocNhHejIU3PrNCppZ6ClQ6MxqnO9fd7B060WFTzD/HXaTc1+6WdwZH6GTxY+QrYK5jrUFkwPbosKtBZFTxH0SkqDBJ2RUsFUbRLUk1zZIvTzIpwWUORCP7eZZ0usVL2CjFLaTLaZUPdnIZemSAh6U7ZhaeaGpa39HXBZDwamamdvisZnoO2Zetz2FdTusM3E+UE3sTm9/+EICud1I7NzS+DbXBuwzXMLtMRkpW0gC88LeQ0gYJOir5SGv/SmbDzagi49PG1uR9ft+Sk6lCZpL8P2zl9n6nE/+//a6/iK7E3aebXJezToeZTSy9hH2G/hmsugETPz1ISZp4bXy4IHbK0Nf0n+wSJLdX6oAIqZ2ehS34bJh/Zu8Pk27G1v27PBx2xr3wvMzns62ibh20myhzN56xpvp16nBMpDNQAvEO+CuSUJnwjJjgpRJF/xsJXTGFt8iyYoOQ+2dAgdqxbNzAHC4ozn+ZSmvZw05hTbojs79OemnGKrpSTHbM7xWNH1PzHnJ3K9Lo7hU57mioyVL1In6Hcx99dNhd1nslFGDmf3QP0w6L+hKDU58DeR7psC50vuNYvu9SFm0MG9bGECnYBvh8c9gSj/paLPLQDNXUoDj6OpolvXuGn+DbTaOUaFeqCRmrVzIROE9oUotKfoHpOhKuiTZIqbC9aLs1oN/qJCAiI05tesw2+PbgCF+dWWObmkAbV2Nc6/qfbDS1JdBmDWagxmhXdJI8qDeIXajIbDFSvRUrwQ9EmtTqUcGY7NAp4GiYStSmINplKoieqBymbFwrjoIwZvcdGzam/R92iGO3fBPH7yrf2de7cOlRVxOq3G7hFXjbWMv3Bfn4nZaRJuhliaZgSzad5i6D1wdrxjW29Daa5Wpy0r3bTzwTX3vT29ych0t1rL7aK/9Ru/fXbQUdNVXcKrbYVlhbblD795uFCfXSfZvbbCLOHI5aMrnGXVZTk6j68/kD949qOn8JjTy47zpShGU6N34gCJ0mStTSJ+ZMUwixnAihqHiBZDVAHkJaEgVnVV5o1odYXRjDyLnKfC3lSB83hS9OwxYgVROGJzkFALKpucHkAl5pNCmgYC28SEY4fF0aioy3mEAOqanmIv6xB66Y9/vYY+3azTqT/S89rf81pdy3L+TxohS9B8ouL3tLbe/BsjoD/9nGZ+psBspKc03M1L9Hs18w+aaYF+vGq+GfoQDAI32BtoJPDGaCcqMkIQisJAQ/5R4iG/4Bbgv8DBMta3Zh/lf4n+3aqsNh2SInFti0pcqxLlra0ihJtwpuwwzIUVFSiidC07UdgZ0giYLSBrQGRP35Sgfu0B9WtVPu1WmKQgfx3YdWaiuMfJ0QZ9dfG5ILNx27yJqF9v3nLm7qYsnV+nfvUHw1+Uss+E1a/J81/i36GKQY28kMLLkZABWlxAMbJghmefzc0v1JDa/VxsExYNLMTGgPhtjhgqKMRigXmgCWGWzTCsGObwsGguQMboNValDCxsBEhIoecm28OxIt4NO85u86ztbrP1TgQe8PcfHqqmvfMfEju6Rl/Yv5xXcdf7+H2Mpm7s6GBXRMj7P61y/VcAAHjaY2BkYGBgZOo//7DZK57f5iuDPAcDCFz2z/KA0f/P/mvhyGTXAHI5GJhAogBrnAx3AAB42mNgZGBg1/gXzcDA8eL/2f/PODIZgCIo4CUAogoHhnjabZNfSJNRGMaf7/z5VjD6A6bQjctWClFgEV1LiVR2FTHnMCjXruY/hCCCRdCwUApyYEWyZDUsKKUspJuI6MYKuggGIl5Eky4WXgQjarGe92uLJX7w4znnPd855z3vc44q4AhqPmcUUCkU1CrmTQZd5K7bhLC9ij7nLeZVDE9IVB9AgmODTgpDahoxalwtln8xdpyUyJUKbeQWGSVJcpHMOitICWzfJ49MxnFUEU3uTQzYZmy2AeTsPVxy65AzL8k4+yX2/cipKH7rKURsB4qmATlfO3ISd88wp1coilo/x/YhbB4jaJexIGv68thq3nlst1twnud4ppbKP6j9zOGj3s2zh9Clv7B/GrM6g25q2NSjW42j0WzECXMSWeZ9x/lc/qBXvXO8cXuQlTgJmw4q5+i9yOpBRNQiDjI+pvPcM48GPYOgFp1EJ/dtUzHHT41z/xtSf6k92xnSXtGQ/GMUrjO3FneY/Rn06QTSHJuWOV4shDodRI94oh6gl0QZ+yR72004pAJ4yP4I47dVifklMGef4prHC5xi7fd4dV8HX2/5m3jh+VADffCR12Qb8bud2F/1YS3Ma9LzRbyoQbwQz8wU3kvd18MdoIoX9f/D2u8kaWelXCDfzVFE/vmwFtal0h6rRbwQz0Q3fGWuy/yHObFWO0izTgG+FqCq6izfyAJp/Qvy1H7qOY7xHVTh2hO8FxN8F0l5I5V3kiSiQ7zvu+xlxGWuuoA0mZN1mWfAPscx/ZPtw7xzI2j8AyV25OAAAAB42mNgYNCBwxaGI4wnmBYxZ7AosXix1LEcYTVhLWPdw3qLjYdNi62L7RK7F/snDgeOT5wpnFO4EriucCtwt3Gv4D7F/YanhDeFdwWfHF8T3yl+Nn4b/kP8vwQkBBIEtgncETQSLBC8ICQl1Cf0RbhOeJ3wJxEVkVuiKqIpon2i+0RviXGJOYlFiTWIC4kXiV+QMJFYI/FPSkEqTWqNNJt0hHSJ9CsZM5lJMj9k42SXySXInZOXkQ9SkFBIUJilcETxjuIPZQnlIiA8ppKk8k41Q/WWGoPaGXU59ScaBRrHNN5pvNPcoHlOS0urQuuBdpJ2l/YzHS2dJJ0zuny6Cbp79CL0hfR/GNQYnDNUMKwxYjOaZKxkPMvEzWSCyR1TA9N1pjfMWMwczBaYc5n3mf+zKLB4YznByswqwuqRtZl1j/UbmxKbI7YitpvsouyZ7Hc4THOscIpxNnG+4ZLm8s21z83LrcZtndsH9wD3Rx4lHs88ozxveFV4S3lneD/z8fLZ4Cvnu8mPyS/B74l/WYBBwJaAV4FWOKBHYFhgSmBN4JTAa0ESQVFBV4J9go8E/wnJAcJFIbdCboW2hf4JkwmrCXsEAOI0m6EAAQAAAOkAZQAFAAAAAAACAAEAAgAWAAABAAGCAAAAAHja1VbNbuNkFL1OO5BJSwUIzYLFyKpYtFJJU9RBqKwQaMRI/GkG0SWT2E5iNYkzsd1MEQsegSUPwBKxYsWCNT9bNrwDj8CCc8+9jpOmw0yRWKAo9vX33d/znXttEbkV7MiGBJs3RYJtEZcDeQVPJjdkJwhd3pD7QdvlTXkt+MrlG/J+8K3Lz8H2T5efl4eNymdTOo2HLt+U242vXW7d+LHxvctb0mkOXd6WuPmNyy8EXzb/cnlHjluPXX5Rmq3vXH5JWq0fXP5ZbrV+cvkX6bR+d/lX2dnadPk32d562eQ/NuTVrdvyrmQylQuZSSoDGUohoexJJPu4vyEdOcI/lB40QuxdyCfQH0lXJhJj5QMp5QxPuXyBp/dwTSXBjt4jrMxxL+A1lPtYz/GfyTk1QrkLTxPG+wgexlgNZRceu1jLILXpX/0k0MvdqmRk9RPSs1o9kHvQDOVjVKK6y75XPRxg5TNa51jPqHuESEcezWKblaGheQ8QVWuePQWBy/WfPMHnyRK2V+2Hl6JelbFZv42nUyJbUEd3I/hQqy6kwpHS2otFrNeXYtXxU2iFeFJc1VpRHtPTGdYy6f8LBrSvbfG03fVsc3o2bqWLLJUJfWKgDOmTYSmyUB7HREwRmDirUiJX86mE9tixu9wFp8REo86BZI+5mpdVv7Nn6I+9FcaHjGnVaC8s57G7yNLQ1PqH6FLl7T1ypmD9CW0No4iZKg7KJKtd87WzMGRyaFrvTSEV7JQCfroLi4is6zNmxL0JKlT9GRk5Y49b5BNmWdDvEHsaN3b+KZtCeYS1lHG0QmOa1jv1XDX6LifH0Hu5XOBr9ffgN/Z5lMhjRutBq6BVHTMmRlNWe7FSaebTTv1pnRXjNa/8H2NbPw4WXZXiJLVuPYVPnT0RtXLuRu5fscqI8IxYZaz5gDtdX4sW/W64nzP/FLWN6HeVoyUsp8wjcgaqN63pnPuV3oidb3Ogz/hj1lh3RMqYoU+NMXO7YG9Zvyb0MVhwRmt9xxk3dA5V81vrGHsuFZo57RNOkfVeHSFexj2dNWfO34TVx86HOlLfp5qtdH3CVzNhTiSe3N9VJx94hGSBqLJmwPeUsTfGimUyYVeExG7EbOeOjfVGiUpmS3maHK8wIif3U0yLGSPZG6yaGAWZN2K0asqun12+crp1zV3mlvCUqs40L3M/T/V24KxOnUv1yRXMyezsqSTCJSupmFudRu5aXbDSuFOscKU62YydM6GFdceQlUwxIQ7xm/PX9kldvx3anDZjaFxX//LszbG2PH0/X5u+h//xt8/etWvY/199Ma1XmMNOsZyy89u0GOGecWYeItpdeN+/gg/PZllVWn+96LdPj71puduX0alX/qFP/lCO8e/geiJ35C1cj3GtzvhNoqOTRedvQXaX7IN8CZUH/uaybh/9DeeiFNJ42m3QV0xTcRTH8e+B0kLZe+Peq/eWMtwt5br3wK0o0FYRsFgVFxrBrdGY+KZxvahxz2jUBzXuFUfUB5/d8UF91cL9++Z5+eT3/+ecnBwiaK8/FZTzv/oEEiGRYiESC1FYsRFNDHZiiSOeBBJJIpkUUkkjnQwyySKbHHLJI58COtCRTnSmC13pRnd60JNe9KYPfelHfwbgQEPHSSEuiiimhFIGMojBDGEowxiOGw9leMM7GoxgJKMYzRjGMo7xTGAik5jMFKYyjelUMIOZzGI2c5jLPOazgEqJ4igttHKD/XxkM7vZwQGOc0ysbOc9m9gnNolml8Swldt8EDsHOcEvfvKbI5ziAfc4zUIWsYcqHlHNfR7yjMc84Wn4TjW85DkvOIOPH+zlDa94jZ8vfGMbiwmwhKXUUsch6llGA0EaCbGcFazkM6tYTRNrWMdarnKYZtazgY185TvXOMs5rvOWdxIrcRIvCZIoSZIsKZIqaZIuGZIpWZznApe5wh0ucom7bOGkZHOTW5IjueyUPMmXAquvtqnBr9lCdQGHw+E1o9OMbofSa+rRlerf41KWtqmH+5WaUlc6lYVKl7JIWawsUf6b5zbV1FxNs9cEfKFgdVVlo9980g1Tl2EpDwXr24PLKGvT8Jh7hNX/AtbOnHEAeNpFzqsOwkAQBdDdlr7pu6SKpOjVCIKlNTUETJuQ4JEILBgkWBzfMEsQhA/iN8qUbhc3507mZl60OQO9kBLMZcUpvda80Fk1gaAuIVnhcKrHoLNNRUDNclDZAqwsfxOV+kRhP5tZ/rC4gIEwdwI6wlgLaAh9LjBAaB8Buyv0+kIHl/ZNYIhw0g4UXPFDiKn7VBhXiwMyQIZbSR8ZTCW9tt+nMyKTqE3cY/NPYjyJ7pIJMt5LjpBJ2rOGhH0Bs3VX7QAAAAABVym5yAAA) format('woff'); + font-weight: normal; + font-style: normal; +} + +/* Links */ +.joint-link.joint-theme-material .connection-wrap { + stroke: #000000; + stroke-width: 15; + stroke-linecap: round; + stroke-linejoin: round; + opacity: 0; + cursor: move; +} +.joint-link.joint-theme-material .connection-wrap:hover { + opacity: .4; + stroke-opacity: .4; +} +.joint-link.joint-theme-material .connection { + stroke-linejoin: round; +} +.joint-link.joint-theme-material .link-tools .tool-remove circle { + fill: #C64242; +} +.joint-link.joint-theme-material .link-tools .tool-remove path { + fill: #FFFFFF; +} + +/* element inside .marker-vertex-group element */ +.joint-link.joint-theme-material .marker-vertex { + fill: #d0d8e8; +} +.joint-link.joint-theme-material .marker-vertex:hover { + fill: #5fa9ee; + stroke: none; +} + +.joint-link.joint-theme-material .marker-arrowhead { + fill: #d0d8e8; +} +.joint-link.joint-theme-material .marker-arrowhead:hover { + fill: #5fa9ee; + stroke: none; +} + +/* element used to remove a vertex */ +.joint-link.joint-theme-material .marker-vertex-remove-area { + fill: #5fa9ee; +} +.joint-link.joint-theme-material .marker-vertex-remove { + fill: white; +} +/* Links */ + +/* Links */ +.joint-link.joint-theme-modern .connection-wrap { + stroke: #000000; + stroke-width: 15; + stroke-linecap: round; + stroke-linejoin: round; + opacity: 0; + cursor: move; +} +.joint-link.joint-theme-modern .connection-wrap:hover { + opacity: .4; + stroke-opacity: .4; +} +.joint-link.joint-theme-modern .connection { + stroke-linejoin: round; +} +.joint-link.joint-theme-modern .link-tools .tool-remove circle { + fill: #FF0000; +} +.joint-link.joint-theme-modern .link-tools .tool-remove path { + fill: #FFFFFF; +} + +/* element inside .marker-vertex-group element */ +.joint-link.joint-theme-modern .marker-vertex { + fill: #1ABC9C; +} +.joint-link.joint-theme-modern .marker-vertex:hover { + fill: #34495E; + stroke: none; +} + +.joint-link.joint-theme-modern .marker-arrowhead { + fill: #1ABC9C; +} +.joint-link.joint-theme-modern .marker-arrowhead:hover { + fill: #F39C12; + stroke: none; +} + +/* element used to remove a vertex */ +.joint-link.joint-theme-modern .marker-vertex-remove { + fill: white; +} +/* Links */ diff --git a/www/js/backbone.js b/www/js/backbone.js new file mode 100644 index 0000000..55ccb22 --- /dev/null +++ b/www/js/backbone.js @@ -0,0 +1,1920 @@ +// Backbone.js 1.3.3 + +// (c) 2010-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors +// Backbone may be freely distributed under the MIT license. +// For all details and documentation: +// http://backbonejs.org + +(function(factory) { + + // Establish the root object, `window` (`self`) in the browser, or `global` on the server. + // We use `self` instead of `window` for `WebWorker` support. + var root = (typeof self == 'object' && self.self === self && self) || + (typeof global == 'object' && global.global === global && global); + + // Set up Backbone appropriately for the environment. Start with AMD. + if (typeof define === 'function' && define.amd) { + define(['underscore', 'jquery', 'exports'], function(_, $, exports) { + // Export global even in AMD case in case this script is loaded with + // others that may still expect a global Backbone. + root.Backbone = factory(root, exports, _, $); + }); + + // Next for Node.js or CommonJS. jQuery may not be needed as a module. + } else if (typeof exports !== 'undefined') { + var _ = require('underscore'), $; + try { $ = require('jquery'); } catch (e) {} + factory(root, exports, _, $); + + // Finally, as a browser global. + } else { + root.Backbone = factory(root, {}, root._, (root.jQuery || root.Zepto || root.ender || root.$)); + } + +})(function(root, Backbone, _, $) { + + // Initial Setup + // ------------- + + // Save the previous value of the `Backbone` variable, so that it can be + // restored later on, if `noConflict` is used. + var previousBackbone = root.Backbone; + + // Create a local reference to a common array method we'll want to use later. + var slice = Array.prototype.slice; + + // Current version of the library. Keep in sync with `package.json`. + Backbone.VERSION = '1.3.3'; + + // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns + // the `$` variable. + Backbone.$ = $; + + // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable + // to its previous owner. Returns a reference to this Backbone object. + Backbone.noConflict = function() { + root.Backbone = previousBackbone; + return this; + }; + + // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option + // will fake `"PATCH"`, `"PUT"` and `"DELETE"` requests via the `_method` parameter and + // set a `X-Http-Method-Override` header. + Backbone.emulateHTTP = false; + + // Turn on `emulateJSON` to support legacy servers that can't deal with direct + // `application/json` requests ... this will encode the body as + // `application/x-www-form-urlencoded` instead and will send the model in a + // form param named `model`. + Backbone.emulateJSON = false; + + // Proxy Backbone class methods to Underscore functions, wrapping the model's + // `attributes` object or collection's `models` array behind the scenes. + // + // collection.filter(function(model) { return model.get('age') > 10 }); + // collection.each(this.addView); + // + // `Function#apply` can be slow so we use the method's arg count, if we know it. + var addMethod = function(length, method, attribute) { + switch (length) { + case 1: return function() { + return _[method](this[attribute]); + }; + case 2: return function(value) { + return _[method](this[attribute], value); + }; + case 3: return function(iteratee, context) { + return _[method](this[attribute], cb(iteratee, this), context); + }; + case 4: return function(iteratee, defaultVal, context) { + return _[method](this[attribute], cb(iteratee, this), defaultVal, context); + }; + default: return function() { + var args = slice.call(arguments); + args.unshift(this[attribute]); + return _[method].apply(_, args); + }; + } + }; + var addUnderscoreMethods = function(Class, methods, attribute) { + _.each(methods, function(length, method) { + if (_[method]) Class.prototype[method] = addMethod(length, method, attribute); + }); + }; + + // Support `collection.sortBy('attr')` and `collection.findWhere({id: 1})`. + var cb = function(iteratee, instance) { + if (_.isFunction(iteratee)) return iteratee; + if (_.isObject(iteratee) && !instance._isModel(iteratee)) return modelMatcher(iteratee); + if (_.isString(iteratee)) return function(model) { return model.get(iteratee); }; + return iteratee; + }; + var modelMatcher = function(attrs) { + var matcher = _.matches(attrs); + return function(model) { + return matcher(model.attributes); + }; + }; + + // Backbone.Events + // --------------- + + // A module that can be mixed in to *any object* in order to provide it with + // a custom event channel. You may bind a callback to an event with `on` or + // remove with `off`; `trigger`-ing an event fires all callbacks in + // succession. + // + // var object = {}; + // _.extend(object, Backbone.Events); + // object.on('expand', function(){ alert('expanded'); }); + // object.trigger('expand'); + // + var Events = Backbone.Events = {}; + + // Regular expression used to split event strings. + var eventSplitter = /\s+/; + + // Iterates over the standard `event, callback` (as well as the fancy multiple + // space-separated events `"change blur", callback` and jQuery-style event + // maps `{event: callback}`). + var eventsApi = function(iteratee, events, name, callback, opts) { + var i = 0, names; + if (name && typeof name === 'object') { + // Handle event maps. + if (callback !== void 0 && 'context' in opts && opts.context === void 0) opts.context = callback; + for (names = _.keys(name); i < names.length ; i++) { + events = eventsApi(iteratee, events, names[i], name[names[i]], opts); + } + } else if (name && eventSplitter.test(name)) { + // Handle space-separated event names by delegating them individually. + for (names = name.split(eventSplitter); i < names.length; i++) { + events = iteratee(events, names[i], callback, opts); + } + } else { + // Finally, standard events. + events = iteratee(events, name, callback, opts); + } + return events; + }; + + // Bind an event to a `callback` function. Passing `"all"` will bind + // the callback to all events fired. + Events.on = function(name, callback, context) { + return internalOn(this, name, callback, context); + }; + + // Guard the `listening` argument from the public API. + var internalOn = function(obj, name, callback, context, listening) { + obj._events = eventsApi(onApi, obj._events || {}, name, callback, { + context: context, + ctx: obj, + listening: listening + }); + + if (listening) { + var listeners = obj._listeners || (obj._listeners = {}); + listeners[listening.id] = listening; + } + + return obj; + }; + + // Inversion-of-control versions of `on`. Tell *this* object to listen to + // an event in another object... keeping track of what it's listening to + // for easier unbinding later. + Events.listenTo = function(obj, name, callback) { + if (!obj) return this; + var id = obj._listenId || (obj._listenId = _.uniqueId('l')); + var listeningTo = this._listeningTo || (this._listeningTo = {}); + var listening = listeningTo[id]; + + // This object is not listening to any other events on `obj` yet. + // Setup the necessary references to track the listening callbacks. + if (!listening) { + var thisId = this._listenId || (this._listenId = _.uniqueId('l')); + listening = listeningTo[id] = {obj: obj, objId: id, id: thisId, listeningTo: listeningTo, count: 0}; + } + + // Bind callbacks on obj, and keep track of them on listening. + internalOn(obj, name, callback, this, listening); + return this; + }; + + // The reducing API that adds a callback to the `events` object. + var onApi = function(events, name, callback, options) { + if (callback) { + var handlers = events[name] || (events[name] = []); + var context = options.context, ctx = options.ctx, listening = options.listening; + if (listening) listening.count++; + + handlers.push({callback: callback, context: context, ctx: context || ctx, listening: listening}); + } + return events; + }; + + // Remove one or many callbacks. If `context` is null, removes all + // callbacks with that function. If `callback` is null, removes all + // callbacks for the event. If `name` is null, removes all bound + // callbacks for all events. + Events.off = function(name, callback, context) { + if (!this._events) return this; + this._events = eventsApi(offApi, this._events, name, callback, { + context: context, + listeners: this._listeners + }); + return this; + }; + + // Tell this object to stop listening to either specific events ... or + // to every object it's currently listening to. + Events.stopListening = function(obj, name, callback) { + var listeningTo = this._listeningTo; + if (!listeningTo) return this; + + var ids = obj ? [obj._listenId] : _.keys(listeningTo); + + for (var i = 0; i < ids.length; i++) { + var listening = listeningTo[ids[i]]; + + // If listening doesn't exist, this object is not currently + // listening to obj. Break out early. + if (!listening) break; + + listening.obj.off(name, callback, this); + } + + return this; + }; + + // The reducing API that removes a callback from the `events` object. + var offApi = function(events, name, callback, options) { + if (!events) return; + + var i = 0, listening; + var context = options.context, listeners = options.listeners; + + // Delete all events listeners and "drop" events. + if (!name && !callback && !context) { + var ids = _.keys(listeners); + for (; i < ids.length; i++) { + listening = listeners[ids[i]]; + delete listeners[listening.id]; + delete listening.listeningTo[listening.objId]; + } + return; + } + + var names = name ? [name] : _.keys(events); + for (; i < names.length; i++) { + name = names[i]; + var handlers = events[name]; + + // Bail out if there are no events stored. + if (!handlers) break; + + // Replace events if there are any remaining. Otherwise, clean up. + var remaining = []; + for (var j = 0; j < handlers.length; j++) { + var handler = handlers[j]; + if ( + callback && callback !== handler.callback && + callback !== handler.callback._callback || + context && context !== handler.context + ) { + remaining.push(handler); + } else { + listening = handler.listening; + if (listening && --listening.count === 0) { + delete listeners[listening.id]; + delete listening.listeningTo[listening.objId]; + } + } + } + + // Update tail event if the list has any events. Otherwise, clean up. + if (remaining.length) { + events[name] = remaining; + } else { + delete events[name]; + } + } + return events; + }; + + // Bind an event to only be triggered a single time. After the first time + // the callback is invoked, its listener will be removed. If multiple events + // are passed in using the space-separated syntax, the handler will fire + // once for each event, not once for a combination of all events. + Events.once = function(name, callback, context) { + // Map the event into a `{event: once}` object. + var events = eventsApi(onceMap, {}, name, callback, _.bind(this.off, this)); + if (typeof name === 'string' && context == null) callback = void 0; + return this.on(events, callback, context); + }; + + // Inversion-of-control versions of `once`. + Events.listenToOnce = function(obj, name, callback) { + // Map the event into a `{event: once}` object. + var events = eventsApi(onceMap, {}, name, callback, _.bind(this.stopListening, this, obj)); + return this.listenTo(obj, events); + }; + + // Reduces the event callbacks into a map of `{event: onceWrapper}`. + // `offer` unbinds the `onceWrapper` after it has been called. + var onceMap = function(map, name, callback, offer) { + if (callback) { + var once = map[name] = _.once(function() { + offer(name, once); + callback.apply(this, arguments); + }); + once._callback = callback; + } + return map; + }; + + // Trigger one or many events, firing all bound callbacks. Callbacks are + // passed the same arguments as `trigger` is, apart from the event name + // (unless you're listening on `"all"`, which will cause your callback to + // receive the true name of the event as the first argument). + Events.trigger = function(name) { + if (!this._events) return this; + + var length = Math.max(0, arguments.length - 1); + var args = Array(length); + for (var i = 0; i < length; i++) args[i] = arguments[i + 1]; + + eventsApi(triggerApi, this._events, name, void 0, args); + return this; + }; + + // Handles triggering the appropriate event callbacks. + var triggerApi = function(objEvents, name, callback, args) { + if (objEvents) { + var events = objEvents[name]; + var allEvents = objEvents.all; + if (events && allEvents) allEvents = allEvents.slice(); + if (events) triggerEvents(events, args); + if (allEvents) triggerEvents(allEvents, [name].concat(args)); + } + return objEvents; + }; + + // A difficult-to-believe, but optimized internal dispatch function for + // triggering events. Tries to keep the usual cases speedy (most internal + // Backbone events have 3 arguments). + var triggerEvents = function(events, args) { + var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2]; + switch (args.length) { + case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return; + case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return; + case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return; + case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return; + default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return; + } + }; + + // Aliases for backwards compatibility. + Events.bind = Events.on; + Events.unbind = Events.off; + + // Allow the `Backbone` object to serve as a global event bus, for folks who + // want global "pubsub" in a convenient place. + _.extend(Backbone, Events); + + // Backbone.Model + // -------------- + + // Backbone **Models** are the basic data object in the framework -- + // frequently representing a row in a table in a database on your server. + // A discrete chunk of data and a bunch of useful, related methods for + // performing computations and transformations on that data. + + // Create a new model with the specified attributes. A client id (`cid`) + // is automatically generated and assigned for you. + var Model = Backbone.Model = function(attributes, options) { + var attrs = attributes || {}; + options || (options = {}); + this.cid = _.uniqueId(this.cidPrefix); + this.attributes = {}; + if (options.collection) this.collection = options.collection; + if (options.parse) attrs = this.parse(attrs, options) || {}; + var defaults = _.result(this, 'defaults'); + attrs = _.defaults(_.extend({}, defaults, attrs), defaults); + this.set(attrs, options); + this.changed = {}; + this.initialize.apply(this, arguments); + }; + + // Attach all inheritable methods to the Model prototype. + _.extend(Model.prototype, Events, { + + // A hash of attributes whose current and previous value differ. + changed: null, + + // The value returned during the last failed validation. + validationError: null, + + // The default name for the JSON `id` attribute is `"id"`. MongoDB and + // CouchDB users may want to set this to `"_id"`. + idAttribute: 'id', + + // The prefix is used to create the client id which is used to identify models locally. + // You may want to override this if you're experiencing name clashes with model ids. + cidPrefix: 'c', + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize: function(){}, + + // Return a copy of the model's `attributes` object. + toJSON: function(options) { + return _.clone(this.attributes); + }, + + // Proxy `Backbone.sync` by default -- but override this if you need + // custom syncing semantics for *this* particular model. + sync: function() { + return Backbone.sync.apply(this, arguments); + }, + + // Get the value of an attribute. + get: function(attr) { + return this.attributes[attr]; + }, + + // Get the HTML-escaped value of an attribute. + escape: function(attr) { + return _.escape(this.get(attr)); + }, + + // Returns `true` if the attribute contains a value that is not null + // or undefined. + has: function(attr) { + return this.get(attr) != null; + }, + + // Special-cased proxy to underscore's `_.matches` method. + matches: function(attrs) { + return !!_.iteratee(attrs, this)(this.attributes); + }, + + // Set a hash of model attributes on the object, firing `"change"`. This is + // the core primitive operation of a model, updating the data and notifying + // anyone who needs to know about the change in state. The heart of the beast. + set: function(key, val, options) { + if (key == null) return this; + + // Handle both `"key", value` and `{key: value}` -style arguments. + var attrs; + if (typeof key === 'object') { + attrs = key; + options = val; + } else { + (attrs = {})[key] = val; + } + + options || (options = {}); + + // Run validation. + if (!this._validate(attrs, options)) return false; + + // Extract attributes and options. + var unset = options.unset; + var silent = options.silent; + var changes = []; + var changing = this._changing; + this._changing = true; + + if (!changing) { + this._previousAttributes = _.clone(this.attributes); + this.changed = {}; + } + + var current = this.attributes; + var changed = this.changed; + var prev = this._previousAttributes; + + // For each `set` attribute, update or delete the current value. + for (var attr in attrs) { + val = attrs[attr]; + if (!_.isEqual(current[attr], val)) changes.push(attr); + if (!_.isEqual(prev[attr], val)) { + changed[attr] = val; + } else { + delete changed[attr]; + } + unset ? delete current[attr] : current[attr] = val; + } + + // Update the `id`. + if (this.idAttribute in attrs) this.id = this.get(this.idAttribute); + + // Trigger all relevant attribute changes. + if (!silent) { + if (changes.length) this._pending = options; + for (var i = 0; i < changes.length; i++) { + this.trigger('change:' + changes[i], this, current[changes[i]], options); + } + } + + // You might be wondering why there's a `while` loop here. Changes can + // be recursively nested within `"change"` events. + if (changing) return this; + if (!silent) { + while (this._pending) { + options = this._pending; + this._pending = false; + this.trigger('change', this, options); + } + } + this._pending = false; + this._changing = false; + return this; + }, + + // Remove an attribute from the model, firing `"change"`. `unset` is a noop + // if the attribute doesn't exist. + unset: function(attr, options) { + return this.set(attr, void 0, _.extend({}, options, {unset: true})); + }, + + // Clear all attributes on the model, firing `"change"`. + clear: function(options) { + var attrs = {}; + for (var key in this.attributes) attrs[key] = void 0; + return this.set(attrs, _.extend({}, options, {unset: true})); + }, + + // Determine if the model has changed since the last `"change"` event. + // If you specify an attribute name, determine if that attribute has changed. + hasChanged: function(attr) { + if (attr == null) return !_.isEmpty(this.changed); + return _.has(this.changed, attr); + }, + + // Return an object containing all the attributes that have changed, or + // false if there are no changed attributes. Useful for determining what + // parts of a view need to be updated and/or what attributes need to be + // persisted to the server. Unset attributes will be set to undefined. + // You can also pass an attributes object to diff against the model, + // determining if there *would be* a change. + changedAttributes: function(diff) { + if (!diff) return this.hasChanged() ? _.clone(this.changed) : false; + var old = this._changing ? this._previousAttributes : this.attributes; + var changed = {}; + for (var attr in diff) { + var val = diff[attr]; + if (_.isEqual(old[attr], val)) continue; + changed[attr] = val; + } + return _.size(changed) ? changed : false; + }, + + // Get the previous value of an attribute, recorded at the time the last + // `"change"` event was fired. + previous: function(attr) { + if (attr == null || !this._previousAttributes) return null; + return this._previousAttributes[attr]; + }, + + // Get all of the attributes of the model at the time of the previous + // `"change"` event. + previousAttributes: function() { + return _.clone(this._previousAttributes); + }, + + // Fetch the model from the server, merging the response with the model's + // local attributes. Any changed attributes will trigger a "change" event. + fetch: function(options) { + options = _.extend({parse: true}, options); + var model = this; + var success = options.success; + options.success = function(resp) { + var serverAttrs = options.parse ? model.parse(resp, options) : resp; + if (!model.set(serverAttrs, options)) return false; + if (success) success.call(options.context, model, resp, options); + model.trigger('sync', model, resp, options); + }; + wrapError(this, options); + return this.sync('read', this, options); + }, + + // Set a hash of model attributes, and sync the model to the server. + // If the server returns an attributes hash that differs, the model's + // state will be `set` again. + save: function(key, val, options) { + // Handle both `"key", value` and `{key: value}` -style arguments. + var attrs; + if (key == null || typeof key === 'object') { + attrs = key; + options = val; + } else { + (attrs = {})[key] = val; + } + + options = _.extend({validate: true, parse: true}, options); + var wait = options.wait; + + // If we're not waiting and attributes exist, save acts as + // `set(attr).save(null, opts)` with validation. Otherwise, check if + // the model will be valid when the attributes, if any, are set. + if (attrs && !wait) { + if (!this.set(attrs, options)) return false; + } else if (!this._validate(attrs, options)) { + return false; + } + + // After a successful server-side save, the client is (optionally) + // updated with the server-side state. + var model = this; + var success = options.success; + var attributes = this.attributes; + options.success = function(resp) { + // Ensure attributes are restored during synchronous saves. + model.attributes = attributes; + var serverAttrs = options.parse ? model.parse(resp, options) : resp; + if (wait) serverAttrs = _.extend({}, attrs, serverAttrs); + if (serverAttrs && !model.set(serverAttrs, options)) return false; + if (success) success.call(options.context, model, resp, options); + model.trigger('sync', model, resp, options); + }; + wrapError(this, options); + + // Set temporary attributes if `{wait: true}` to properly find new ids. + if (attrs && wait) this.attributes = _.extend({}, attributes, attrs); + + var method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update'); + if (method === 'patch' && !options.attrs) options.attrs = attrs; + var xhr = this.sync(method, this, options); + + // Restore attributes. + this.attributes = attributes; + + return xhr; + }, + + // Destroy this model on the server if it was already persisted. + // Optimistically removes the model from its collection, if it has one. + // If `wait: true` is passed, waits for the server to respond before removal. + destroy: function(options) { + options = options ? _.clone(options) : {}; + var model = this; + var success = options.success; + var wait = options.wait; + + var destroy = function() { + model.stopListening(); + model.trigger('destroy', model, model.collection, options); + }; + + options.success = function(resp) { + if (wait) destroy(); + if (success) success.call(options.context, model, resp, options); + if (!model.isNew()) model.trigger('sync', model, resp, options); + }; + + var xhr = false; + if (this.isNew()) { + _.defer(options.success); + } else { + wrapError(this, options); + xhr = this.sync('delete', this, options); + } + if (!wait) destroy(); + return xhr; + }, + + // Default URL for the model's representation on the server -- if you're + // using Backbone's restful methods, override this to change the endpoint + // that will be called. + url: function() { + var base = + _.result(this, 'urlRoot') || + _.result(this.collection, 'url') || + urlError(); + if (this.isNew()) return base; + var id = this.get(this.idAttribute); + return base.replace(/[^\/]$/, '$&/') + encodeURIComponent(id); + }, + + // **parse** converts a response into the hash of attributes to be `set` on + // the model. The default implementation is just to pass the response along. + parse: function(resp, options) { + return resp; + }, + + // Create a new model with identical attributes to this one. + clone: function() { + return new this.constructor(this.attributes); + }, + + // A model is new if it has never been saved to the server, and lacks an id. + isNew: function() { + return !this.has(this.idAttribute); + }, + + // Check if the model is currently in a valid state. + isValid: function(options) { + return this._validate({}, _.extend({}, options, {validate: true})); + }, + + // Run validation against the next complete set of model attributes, + // returning `true` if all is well. Otherwise, fire an `"invalid"` event. + _validate: function(attrs, options) { + if (!options.validate || !this.validate) return true; + attrs = _.extend({}, this.attributes, attrs); + var error = this.validationError = this.validate(attrs, options) || null; + if (!error) return true; + this.trigger('invalid', this, error, _.extend(options, {validationError: error})); + return false; + } + + }); + + // Underscore methods that we want to implement on the Model, mapped to the + // number of arguments they take. + var modelMethods = {keys: 1, values: 1, pairs: 1, invert: 1, pick: 0, + omit: 0, chain: 1, isEmpty: 1}; + + // Mix in each Underscore method as a proxy to `Model#attributes`. + addUnderscoreMethods(Model, modelMethods, 'attributes'); + + // Backbone.Collection + // ------------------- + + // If models tend to represent a single row of data, a Backbone Collection is + // more analogous to a table full of data ... or a small slice or page of that + // table, or a collection of rows that belong together for a particular reason + // -- all of the messages in this particular folder, all of the documents + // belonging to this particular author, and so on. Collections maintain + // indexes of their models, both in order, and for lookup by `id`. + + // Create a new **Collection**, perhaps to contain a specific type of `model`. + // If a `comparator` is specified, the Collection will maintain + // its models in sort order, as they're added and removed. + var Collection = Backbone.Collection = function(models, options) { + options || (options = {}); + if (options.model) this.model = options.model; + if (options.comparator !== void 0) this.comparator = options.comparator; + this._reset(); + this.initialize.apply(this, arguments); + if (models) this.reset(models, _.extend({silent: true}, options)); + }; + + // Default options for `Collection#set`. + var setOptions = {add: true, remove: true, merge: true}; + var addOptions = {add: true, remove: false}; + + // Splices `insert` into `array` at index `at`. + var splice = function(array, insert, at) { + at = Math.min(Math.max(at, 0), array.length); + var tail = Array(array.length - at); + var length = insert.length; + var i; + for (i = 0; i < tail.length; i++) tail[i] = array[i + at]; + for (i = 0; i < length; i++) array[i + at] = insert[i]; + for (i = 0; i < tail.length; i++) array[i + length + at] = tail[i]; + }; + + // Define the Collection's inheritable methods. + _.extend(Collection.prototype, Events, { + + // The default model for a collection is just a **Backbone.Model**. + // This should be overridden in most cases. + model: Model, + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize: function(){}, + + // The JSON representation of a Collection is an array of the + // models' attributes. + toJSON: function(options) { + return this.map(function(model) { return model.toJSON(options); }); + }, + + // Proxy `Backbone.sync` by default. + sync: function() { + return Backbone.sync.apply(this, arguments); + }, + + // Add a model, or list of models to the set. `models` may be Backbone + // Models or raw JavaScript objects to be converted to Models, or any + // combination of the two. + add: function(models, options) { + return this.set(models, _.extend({merge: false}, options, addOptions)); + }, + + // Remove a model, or a list of models from the set. + remove: function(models, options) { + options = _.extend({}, options); + var singular = !_.isArray(models); + models = singular ? [models] : models.slice(); + var removed = this._removeModels(models, options); + if (!options.silent && removed.length) { + options.changes = {added: [], merged: [], removed: removed}; + this.trigger('update', this, options); + } + return singular ? removed[0] : removed; + }, + + // Update a collection by `set`-ing a new list of models, adding new ones, + // removing models that are no longer present, and merging models that + // already exist in the collection, as necessary. Similar to **Model#set**, + // the core operation for updating the data contained by the collection. + set: function(models, options) { + if (models == null) return; + + options = _.extend({}, setOptions, options); + if (options.parse && !this._isModel(models)) { + models = this.parse(models, options) || []; + } + + var singular = !_.isArray(models); + models = singular ? [models] : models.slice(); + + var at = options.at; + if (at != null) at = +at; + if (at > this.length) at = this.length; + if (at < 0) at += this.length + 1; + + var set = []; + var toAdd = []; + var toMerge = []; + var toRemove = []; + var modelMap = {}; + + var add = options.add; + var merge = options.merge; + var remove = options.remove; + + var sort = false; + var sortable = this.comparator && at == null && options.sort !== false; + var sortAttr = _.isString(this.comparator) ? this.comparator : null; + + // Turn bare objects into model references, and prevent invalid models + // from being added. + var model, i; + for (i = 0; i < models.length; i++) { + model = models[i]; + + // If a duplicate is found, prevent it from being added and + // optionally merge it into the existing model. + var existing = this.get(model); + if (existing) { + if (merge && model !== existing) { + var attrs = this._isModel(model) ? model.attributes : model; + if (options.parse) attrs = existing.parse(attrs, options); + existing.set(attrs, options); + toMerge.push(existing); + if (sortable && !sort) sort = existing.hasChanged(sortAttr); + } + if (!modelMap[existing.cid]) { + modelMap[existing.cid] = true; + set.push(existing); + } + models[i] = existing; + + // If this is a new, valid model, push it to the `toAdd` list. + } else if (add) { + model = models[i] = this._prepareModel(model, options); + if (model) { + toAdd.push(model); + this._addReference(model, options); + modelMap[model.cid] = true; + set.push(model); + } + } + } + + // Remove stale models. + if (remove) { + for (i = 0; i < this.length; i++) { + model = this.models[i]; + if (!modelMap[model.cid]) toRemove.push(model); + } + if (toRemove.length) this._removeModels(toRemove, options); + } + + // See if sorting is needed, update `length` and splice in new models. + var orderChanged = false; + var replace = !sortable && add && remove; + if (set.length && replace) { + orderChanged = this.length !== set.length || _.some(this.models, function(m, index) { + return m !== set[index]; + }); + this.models.length = 0; + splice(this.models, set, 0); + this.length = this.models.length; + } else if (toAdd.length) { + if (sortable) sort = true; + splice(this.models, toAdd, at == null ? this.length : at); + this.length = this.models.length; + } + + // Silently sort the collection if appropriate. + if (sort) this.sort({silent: true}); + + // Unless silenced, it's time to fire all appropriate add/sort/update events. + if (!options.silent) { + for (i = 0; i < toAdd.length; i++) { + if (at != null) options.index = at + i; + model = toAdd[i]; + model.trigger('add', model, this, options); + } + if (sort || orderChanged) this.trigger('sort', this, options); + if (toAdd.length || toRemove.length || toMerge.length) { + options.changes = { + added: toAdd, + removed: toRemove, + merged: toMerge + }; + this.trigger('update', this, options); + } + } + + // Return the added (or merged) model (or models). + return singular ? models[0] : models; + }, + + // When you have more items than you want to add or remove individually, + // you can reset the entire set with a new list of models, without firing + // any granular `add` or `remove` events. Fires `reset` when finished. + // Useful for bulk operations and optimizations. + reset: function(models, options) { + options = options ? _.clone(options) : {}; + for (var i = 0; i < this.models.length; i++) { + this._removeReference(this.models[i], options); + } + options.previousModels = this.models; + this._reset(); + models = this.add(models, _.extend({silent: true}, options)); + if (!options.silent) this.trigger('reset', this, options); + return models; + }, + + // Add a model to the end of the collection. + push: function(model, options) { + return this.add(model, _.extend({at: this.length}, options)); + }, + + // Remove a model from the end of the collection. + pop: function(options) { + var model = this.at(this.length - 1); + return this.remove(model, options); + }, + + // Add a model to the beginning of the collection. + unshift: function(model, options) { + return this.add(model, _.extend({at: 0}, options)); + }, + + // Remove a model from the beginning of the collection. + shift: function(options) { + var model = this.at(0); + return this.remove(model, options); + }, + + // Slice out a sub-array of models from the collection. + slice: function() { + return slice.apply(this.models, arguments); + }, + + // Get a model from the set by id, cid, model object with id or cid + // properties, or an attributes object that is transformed through modelId. + get: function(obj) { + if (obj == null) return void 0; + return this._byId[obj] || + this._byId[this.modelId(obj.attributes || obj)] || + obj.cid && this._byId[obj.cid]; + }, + + // Returns `true` if the model is in the collection. + has: function(obj) { + return this.get(obj) != null; + }, + + // Get the model at the given index. + at: function(index) { + if (index < 0) index += this.length; + return this.models[index]; + }, + + // Return models with matching attributes. Useful for simple cases of + // `filter`. + where: function(attrs, first) { + return this[first ? 'find' : 'filter'](attrs); + }, + + // Return the first model with matching attributes. Useful for simple cases + // of `find`. + findWhere: function(attrs) { + return this.where(attrs, true); + }, + + // Force the collection to re-sort itself. You don't need to call this under + // normal circumstances, as the set will maintain sort order as each item + // is added. + sort: function(options) { + var comparator = this.comparator; + if (!comparator) throw new Error('Cannot sort a set without a comparator'); + options || (options = {}); + + var length = comparator.length; + if (_.isFunction(comparator)) comparator = _.bind(comparator, this); + + // Run sort based on type of `comparator`. + if (length === 1 || _.isString(comparator)) { + this.models = this.sortBy(comparator); + } else { + this.models.sort(comparator); + } + if (!options.silent) this.trigger('sort', this, options); + return this; + }, + + // Pluck an attribute from each model in the collection. + pluck: function(attr) { + return this.map(attr + ''); + }, + + // Fetch the default set of models for this collection, resetting the + // collection when they arrive. If `reset: true` is passed, the response + // data will be passed through the `reset` method instead of `set`. + fetch: function(options) { + options = _.extend({parse: true}, options); + var success = options.success; + var collection = this; + options.success = function(resp) { + var method = options.reset ? 'reset' : 'set'; + collection[method](resp, options); + if (success) success.call(options.context, collection, resp, options); + collection.trigger('sync', collection, resp, options); + }; + wrapError(this, options); + return this.sync('read', this, options); + }, + + // Create a new instance of a model in this collection. Add the model to the + // collection immediately, unless `wait: true` is passed, in which case we + // wait for the server to agree. + create: function(model, options) { + options = options ? _.clone(options) : {}; + var wait = options.wait; + model = this._prepareModel(model, options); + if (!model) return false; + if (!wait) this.add(model, options); + var collection = this; + var success = options.success; + options.success = function(m, resp, callbackOpts) { + if (wait) collection.add(m, callbackOpts); + if (success) success.call(callbackOpts.context, m, resp, callbackOpts); + }; + model.save(null, options); + return model; + }, + + // **parse** converts a response into a list of models to be added to the + // collection. The default implementation is just to pass it through. + parse: function(resp, options) { + return resp; + }, + + // Create a new collection with an identical list of models as this one. + clone: function() { + return new this.constructor(this.models, { + model: this.model, + comparator: this.comparator + }); + }, + + // Define how to uniquely identify models in the collection. + modelId: function(attrs) { + return attrs[this.model.prototype.idAttribute || 'id']; + }, + + // Private method to reset all internal state. Called when the collection + // is first initialized or reset. + _reset: function() { + this.length = 0; + this.models = []; + this._byId = {}; + }, + + // Prepare a hash of attributes (or other model) to be added to this + // collection. + _prepareModel: function(attrs, options) { + if (this._isModel(attrs)) { + if (!attrs.collection) attrs.collection = this; + return attrs; + } + options = options ? _.clone(options) : {}; + options.collection = this; + var model = new this.model(attrs, options); + if (!model.validationError) return model; + this.trigger('invalid', this, model.validationError, options); + return false; + }, + + // Internal method called by both remove and set. + _removeModels: function(models, options) { + var removed = []; + for (var i = 0; i < models.length; i++) { + var model = this.get(models[i]); + if (!model) continue; + + var index = this.indexOf(model); + this.models.splice(index, 1); + this.length--; + + // Remove references before triggering 'remove' event to prevent an + // infinite loop. #3693 + delete this._byId[model.cid]; + var id = this.modelId(model.attributes); + if (id != null) delete this._byId[id]; + + if (!options.silent) { + options.index = index; + model.trigger('remove', model, this, options); + } + + removed.push(model); + this._removeReference(model, options); + } + return removed; + }, + + // Method for checking whether an object should be considered a model for + // the purposes of adding to the collection. + _isModel: function(model) { + return model instanceof Model; + }, + + // Internal method to create a model's ties to a collection. + _addReference: function(model, options) { + this._byId[model.cid] = model; + var id = this.modelId(model.attributes); + if (id != null) this._byId[id] = model; + model.on('all', this._onModelEvent, this); + }, + + // Internal method to sever a model's ties to a collection. + _removeReference: function(model, options) { + delete this._byId[model.cid]; + var id = this.modelId(model.attributes); + if (id != null) delete this._byId[id]; + if (this === model.collection) delete model.collection; + model.off('all', this._onModelEvent, this); + }, + + // Internal method called every time a model in the set fires an event. + // Sets need to update their indexes when models change ids. All other + // events simply proxy through. "add" and "remove" events that originate + // in other collections are ignored. + _onModelEvent: function(event, model, collection, options) { + if (model) { + if ((event === 'add' || event === 'remove') && collection !== this) return; + if (event === 'destroy') this.remove(model, options); + if (event === 'change') { + var prevId = this.modelId(model.previousAttributes()); + var id = this.modelId(model.attributes); + if (prevId !== id) { + if (prevId != null) delete this._byId[prevId]; + if (id != null) this._byId[id] = model; + } + } + } + this.trigger.apply(this, arguments); + } + + }); + + // Underscore methods that we want to implement on the Collection. + // 90% of the core usefulness of Backbone Collections is actually implemented + // right here: + var collectionMethods = {forEach: 3, each: 3, map: 3, collect: 3, reduce: 0, + foldl: 0, inject: 0, reduceRight: 0, foldr: 0, find: 3, detect: 3, filter: 3, + select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 3, includes: 3, + contains: 3, invoke: 0, max: 3, min: 3, toArray: 1, size: 1, first: 3, + head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3, + without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3, + isEmpty: 1, chain: 1, sample: 3, partition: 3, groupBy: 3, countBy: 3, + sortBy: 3, indexBy: 3, findIndex: 3, findLastIndex: 3}; + + // Mix in each Underscore method as a proxy to `Collection#models`. + addUnderscoreMethods(Collection, collectionMethods, 'models'); + + // Backbone.View + // ------------- + + // Backbone Views are almost more convention than they are actual code. A View + // is simply a JavaScript object that represents a logical chunk of UI in the + // DOM. This might be a single item, an entire list, a sidebar or panel, or + // even the surrounding frame which wraps your whole app. Defining a chunk of + // UI as a **View** allows you to define your DOM events declaratively, without + // having to worry about render order ... and makes it easy for the view to + // react to specific changes in the state of your models. + + // Creating a Backbone.View creates its initial element outside of the DOM, + // if an existing element is not provided... + var View = Backbone.View = function(options) { + this.cid = _.uniqueId('view'); + _.extend(this, _.pick(options, viewOptions)); + this._ensureElement(); + this.initialize.apply(this, arguments); + }; + + // Cached regex to split keys for `delegate`. + var delegateEventSplitter = /^(\S+)\s*(.*)$/; + + // List of view options to be set as properties. + var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events']; + + // Set up all inheritable **Backbone.View** properties and methods. + _.extend(View.prototype, Events, { + + // The default `tagName` of a View's element is `"div"`. + tagName: 'div', + + // jQuery delegate for element lookup, scoped to DOM elements within the + // current view. This should be preferred to global lookups where possible. + $: function(selector) { + return this.$el.find(selector); + }, + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize: function(){}, + + // **render** is the core function that your view should override, in order + // to populate its element (`this.el`), with the appropriate HTML. The + // convention is for **render** to always return `this`. + render: function() { + return this; + }, + + // Remove this view by taking the element out of the DOM, and removing any + // applicable Backbone.Events listeners. + remove: function() { + this._removeElement(); + this.stopListening(); + return this; + }, + + // Remove this view's element from the document and all event listeners + // attached to it. Exposed for subclasses using an alternative DOM + // manipulation API. + _removeElement: function() { + this.$el.remove(); + }, + + // Change the view's element (`this.el` property) and re-delegate the + // view's events on the new element. + setElement: function(element) { + this.undelegateEvents(); + this._setElement(element); + this.delegateEvents(); + return this; + }, + + // Creates the `this.el` and `this.$el` references for this view using the + // given `el`. `el` can be a CSS selector or an HTML string, a jQuery + // context or an element. Subclasses can override this to utilize an + // alternative DOM manipulation API and are only required to set the + // `this.el` property. + _setElement: function(el) { + this.$el = el instanceof Backbone.$ ? el : Backbone.$(el); + this.el = this.$el[0]; + }, + + // Set callbacks, where `this.events` is a hash of + // + // *{"event selector": "callback"}* + // + // { + // 'mousedown .title': 'edit', + // 'click .button': 'save', + // 'click .open': function(e) { ... } + // } + // + // pairs. Callbacks will be bound to the view, with `this` set properly. + // Uses event delegation for efficiency. + // Omitting the selector binds the event to `this.el`. + delegateEvents: function(events) { + events || (events = _.result(this, 'events')); + if (!events) return this; + this.undelegateEvents(); + for (var key in events) { + var method = events[key]; + if (!_.isFunction(method)) method = this[method]; + if (!method) continue; + var match = key.match(delegateEventSplitter); + this.delegate(match[1], match[2], _.bind(method, this)); + } + return this; + }, + + // Add a single event listener to the view's element (or a child element + // using `selector`). This only works for delegate-able events: not `focus`, + // `blur`, and not `change`, `submit`, and `reset` in Internet Explorer. + delegate: function(eventName, selector, listener) { + this.$el.on(eventName + '.delegateEvents' + this.cid, selector, listener); + return this; + }, + + // Clears all callbacks previously bound to the view by `delegateEvents`. + // You usually don't need to use this, but may wish to if you have multiple + // Backbone views attached to the same DOM element. + undelegateEvents: function() { + if (this.$el) this.$el.off('.delegateEvents' + this.cid); + return this; + }, + + // A finer-grained `undelegateEvents` for removing a single delegated event. + // `selector` and `listener` are both optional. + undelegate: function(eventName, selector, listener) { + this.$el.off(eventName + '.delegateEvents' + this.cid, selector, listener); + return this; + }, + + // Produces a DOM element to be assigned to your view. Exposed for + // subclasses using an alternative DOM manipulation API. + _createElement: function(tagName) { + return document.createElement(tagName); + }, + + // Ensure that the View has a DOM element to render into. + // If `this.el` is a string, pass it through `$()`, take the first + // matching element, and re-assign it to `el`. Otherwise, create + // an element from the `id`, `className` and `tagName` properties. + _ensureElement: function() { + if (!this.el) { + var attrs = _.extend({}, _.result(this, 'attributes')); + if (this.id) attrs.id = _.result(this, 'id'); + if (this.className) attrs['class'] = _.result(this, 'className'); + this.setElement(this._createElement(_.result(this, 'tagName'))); + this._setAttributes(attrs); + } else { + this.setElement(_.result(this, 'el')); + } + }, + + // Set attributes from a hash on this view's element. Exposed for + // subclasses using an alternative DOM manipulation API. + _setAttributes: function(attributes) { + this.$el.attr(attributes); + } + + }); + + // Backbone.sync + // ------------- + + // Override this function to change the manner in which Backbone persists + // models to the server. You will be passed the type of request, and the + // model in question. By default, makes a RESTful Ajax request + // to the model's `url()`. Some possible customizations could be: + // + // * Use `setTimeout` to batch rapid-fire updates into a single request. + // * Send up the models as XML instead of JSON. + // * Persist models via WebSockets instead of Ajax. + // + // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests + // as `POST`, with a `_method` parameter containing the true HTTP method, + // as well as all requests with the body as `application/x-www-form-urlencoded` + // instead of `application/json` with the model in a param named `model`. + // Useful when interfacing with server-side languages like **PHP** that make + // it difficult to read the body of `PUT` requests. + Backbone.sync = function(method, model, options) { + var type = methodMap[method]; + + // Default options, unless specified. + _.defaults(options || (options = {}), { + emulateHTTP: Backbone.emulateHTTP, + emulateJSON: Backbone.emulateJSON + }); + + // Default JSON-request options. + var params = {type: type, dataType: 'json'}; + + // Ensure that we have a URL. + if (!options.url) { + params.url = _.result(model, 'url') || urlError(); + } + + // Ensure that we have the appropriate request data. + if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) { + params.contentType = 'application/json'; + params.data = JSON.stringify(options.attrs || model.toJSON(options)); + } + + // For older servers, emulate JSON by encoding the request into an HTML-form. + if (options.emulateJSON) { + params.contentType = 'application/x-www-form-urlencoded'; + params.data = params.data ? {model: params.data} : {}; + } + + // For older servers, emulate HTTP by mimicking the HTTP method with `_method` + // And an `X-HTTP-Method-Override` header. + if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) { + params.type = 'POST'; + if (options.emulateJSON) params.data._method = type; + var beforeSend = options.beforeSend; + options.beforeSend = function(xhr) { + xhr.setRequestHeader('X-HTTP-Method-Override', type); + if (beforeSend) return beforeSend.apply(this, arguments); + }; + } + + // Don't process data on a non-GET request. + if (params.type !== 'GET' && !options.emulateJSON) { + params.processData = false; + } + + // Pass along `textStatus` and `errorThrown` from jQuery. + var error = options.error; + options.error = function(xhr, textStatus, errorThrown) { + options.textStatus = textStatus; + options.errorThrown = errorThrown; + if (error) error.call(options.context, xhr, textStatus, errorThrown); + }; + + // Make the request, allowing the user to override any Ajax options. + var xhr = options.xhr = Backbone.ajax(_.extend(params, options)); + model.trigger('request', model, xhr, options); + return xhr; + }; + + // Map from CRUD to HTTP for our default `Backbone.sync` implementation. + var methodMap = { + 'create': 'POST', + 'update': 'PUT', + 'patch': 'PATCH', + 'delete': 'DELETE', + 'read': 'GET' + }; + + // Set the default implementation of `Backbone.ajax` to proxy through to `$`. + // Override this if you'd like to use a different library. + Backbone.ajax = function() { + return Backbone.$.ajax.apply(Backbone.$, arguments); + }; + + // Backbone.Router + // --------------- + + // Routers map faux-URLs to actions, and fire events when routes are + // matched. Creating a new one sets its `routes` hash, if not set statically. + var Router = Backbone.Router = function(options) { + options || (options = {}); + if (options.routes) this.routes = options.routes; + this._bindRoutes(); + this.initialize.apply(this, arguments); + }; + + // Cached regular expressions for matching named param parts and splatted + // parts of route strings. + var optionalParam = /\((.*?)\)/g; + var namedParam = /(\(\?)?:\w+/g; + var splatParam = /\*\w+/g; + var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; + + // Set up all inheritable **Backbone.Router** properties and methods. + _.extend(Router.prototype, Events, { + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize: function(){}, + + // Manually bind a single named route to a callback. For example: + // + // this.route('search/:query/p:num', 'search', function(query, num) { + // ... + // }); + // + route: function(route, name, callback) { + if (!_.isRegExp(route)) route = this._routeToRegExp(route); + if (_.isFunction(name)) { + callback = name; + name = ''; + } + if (!callback) callback = this[name]; + var router = this; + Backbone.history.route(route, function(fragment) { + var args = router._extractParameters(route, fragment); + if (router.execute(callback, args, name) !== false) { + router.trigger.apply(router, ['route:' + name].concat(args)); + router.trigger('route', name, args); + Backbone.history.trigger('route', router, name, args); + } + }); + return this; + }, + + // Execute a route handler with the provided parameters. This is an + // excellent place to do pre-route setup or post-route cleanup. + execute: function(callback, args, name) { + if (callback) callback.apply(this, args); + }, + + // Simple proxy to `Backbone.history` to save a fragment into the history. + navigate: function(fragment, options) { + Backbone.history.navigate(fragment, options); + return this; + }, + + // Bind all defined routes to `Backbone.history`. We have to reverse the + // order of the routes here to support behavior where the most general + // routes can be defined at the bottom of the route map. + _bindRoutes: function() { + if (!this.routes) return; + this.routes = _.result(this, 'routes'); + var route, routes = _.keys(this.routes); + while ((route = routes.pop()) != null) { + this.route(route, this.routes[route]); + } + }, + + // Convert a route string into a regular expression, suitable for matching + // against the current location hash. + _routeToRegExp: function(route) { + route = route.replace(escapeRegExp, '\\$&') + .replace(optionalParam, '(?:$1)?') + .replace(namedParam, function(match, optional) { + return optional ? match : '([^/?]+)'; + }) + .replace(splatParam, '([^?]*?)'); + return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$'); + }, + + // Given a route, and a URL fragment that it matches, return the array of + // extracted decoded parameters. Empty or unmatched parameters will be + // treated as `null` to normalize cross-browser behavior. + _extractParameters: function(route, fragment) { + var params = route.exec(fragment).slice(1); + return _.map(params, function(param, i) { + // Don't decode the search params. + if (i === params.length - 1) return param || null; + return param ? decodeURIComponent(param) : null; + }); + } + + }); + + // Backbone.History + // ---------------- + + // Handles cross-browser history management, based on either + // [pushState](http://diveintohtml5.info/history.html) and real URLs, or + // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange) + // and URL fragments. If the browser supports neither (old IE, natch), + // falls back to polling. + var History = Backbone.History = function() { + this.handlers = []; + this.checkUrl = _.bind(this.checkUrl, this); + + // Ensure that `History` can be used outside of the browser. + if (typeof window !== 'undefined') { + this.location = window.location; + this.history = window.history; + } + }; + + // Cached regex for stripping a leading hash/slash and trailing space. + var routeStripper = /^[#\/]|\s+$/g; + + // Cached regex for stripping leading and trailing slashes. + var rootStripper = /^\/+|\/+$/g; + + // Cached regex for stripping urls of hash. + var pathStripper = /#.*$/; + + // Has the history handling already been started? + History.started = false; + + // Set up all inheritable **Backbone.History** properties and methods. + _.extend(History.prototype, Events, { + + // The default interval to poll for hash changes, if necessary, is + // twenty times a second. + interval: 50, + + // Are we at the app root? + atRoot: function() { + var path = this.location.pathname.replace(/[^\/]$/, '$&/'); + return path === this.root && !this.getSearch(); + }, + + // Does the pathname match the root? + matchRoot: function() { + var path = this.decodeFragment(this.location.pathname); + var rootPath = path.slice(0, this.root.length - 1) + '/'; + return rootPath === this.root; + }, + + // Unicode characters in `location.pathname` are percent encoded so they're + // decoded for comparison. `%25` should not be decoded since it may be part + // of an encoded parameter. + decodeFragment: function(fragment) { + return decodeURI(fragment.replace(/%25/g, '%2525')); + }, + + // In IE6, the hash fragment and search params are incorrect if the + // fragment contains `?`. + getSearch: function() { + var match = this.location.href.replace(/#.*/, '').match(/\?.+/); + return match ? match[0] : ''; + }, + + // Gets the true hash value. Cannot use location.hash directly due to bug + // in Firefox where location.hash will always be decoded. + getHash: function(window) { + var match = (window || this).location.href.match(/#(.*)$/); + return match ? match[1] : ''; + }, + + // Get the pathname and search params, without the root. + getPath: function() { + var path = this.decodeFragment( + this.location.pathname + this.getSearch() + ).slice(this.root.length - 1); + return path.charAt(0) === '/' ? path.slice(1) : path; + }, + + // Get the cross-browser normalized URL fragment from the path or hash. + getFragment: function(fragment) { + if (fragment == null) { + if (this._usePushState || !this._wantsHashChange) { + fragment = this.getPath(); + } else { + fragment = this.getHash(); + } + } + return fragment.replace(routeStripper, ''); + }, + + // Start the hash change handling, returning `true` if the current URL matches + // an existing route, and `false` otherwise. + start: function(options) { + if (History.started) throw new Error('Backbone.history has already been started'); + History.started = true; + + // Figure out the initial configuration. Do we need an iframe? + // Is pushState desired ... is it available? + this.options = _.extend({root: '/'}, this.options, options); + this.root = this.options.root; + this._wantsHashChange = this.options.hashChange !== false; + this._hasHashChange = 'onhashchange' in window && (document.documentMode === void 0 || document.documentMode > 7); + this._useHashChange = this._wantsHashChange && this._hasHashChange; + this._wantsPushState = !!this.options.pushState; + this._hasPushState = !!(this.history && this.history.pushState); + this._usePushState = this._wantsPushState && this._hasPushState; + this.fragment = this.getFragment(); + + // Normalize root to always include a leading and trailing slash. + this.root = ('/' + this.root + '/').replace(rootStripper, '/'); + + // Transition from hashChange to pushState or vice versa if both are + // requested. + if (this._wantsHashChange && this._wantsPushState) { + + // If we've started off with a route from a `pushState`-enabled + // browser, but we're currently in a browser that doesn't support it... + if (!this._hasPushState && !this.atRoot()) { + var rootPath = this.root.slice(0, -1) || '/'; + this.location.replace(rootPath + '#' + this.getPath()); + // Return immediately as browser will do redirect to new url + return true; + + // Or if we've started out with a hash-based route, but we're currently + // in a browser where it could be `pushState`-based instead... + } else if (this._hasPushState && this.atRoot()) { + this.navigate(this.getHash(), {replace: true}); + } + + } + + // Proxy an iframe to handle location events if the browser doesn't + // support the `hashchange` event, HTML5 history, or the user wants + // `hashChange` but not `pushState`. + if (!this._hasHashChange && this._wantsHashChange && !this._usePushState) { + this.iframe = document.createElement('iframe'); + this.iframe.src = 'javascript:0'; + this.iframe.style.display = 'none'; + this.iframe.tabIndex = -1; + var body = document.body; + // Using `appendChild` will throw on IE < 9 if the document is not ready. + var iWindow = body.insertBefore(this.iframe, body.firstChild).contentWindow; + iWindow.document.open(); + iWindow.document.close(); + iWindow.location.hash = '#' + this.fragment; + } + + // Add a cross-platform `addEventListener` shim for older browsers. + var addEventListener = window.addEventListener || function(eventName, listener) { + return attachEvent('on' + eventName, listener); + }; + + // Depending on whether we're using pushState or hashes, and whether + // 'onhashchange' is supported, determine how we check the URL state. + if (this._usePushState) { + addEventListener('popstate', this.checkUrl, false); + } else if (this._useHashChange && !this.iframe) { + addEventListener('hashchange', this.checkUrl, false); + } else if (this._wantsHashChange) { + this._checkUrlInterval = setInterval(this.checkUrl, this.interval); + } + + if (!this.options.silent) return this.loadUrl(); + }, + + // Disable Backbone.history, perhaps temporarily. Not useful in a real app, + // but possibly useful for unit testing Routers. + stop: function() { + // Add a cross-platform `removeEventListener` shim for older browsers. + var removeEventListener = window.removeEventListener || function(eventName, listener) { + return detachEvent('on' + eventName, listener); + }; + + // Remove window listeners. + if (this._usePushState) { + removeEventListener('popstate', this.checkUrl, false); + } else if (this._useHashChange && !this.iframe) { + removeEventListener('hashchange', this.checkUrl, false); + } + + // Clean up the iframe if necessary. + if (this.iframe) { + document.body.removeChild(this.iframe); + this.iframe = null; + } + + // Some environments will throw when clearing an undefined interval. + if (this._checkUrlInterval) clearInterval(this._checkUrlInterval); + History.started = false; + }, + + // Add a route to be tested when the fragment changes. Routes added later + // may override previous routes. + route: function(route, callback) { + this.handlers.unshift({route: route, callback: callback}); + }, + + // Checks the current URL to see if it has changed, and if it has, + // calls `loadUrl`, normalizing across the hidden iframe. + checkUrl: function(e) { + var current = this.getFragment(); + + // If the user pressed the back button, the iframe's hash will have + // changed and we should use that for comparison. + if (current === this.fragment && this.iframe) { + current = this.getHash(this.iframe.contentWindow); + } + + if (current === this.fragment) return false; + if (this.iframe) this.navigate(current); + this.loadUrl(); + }, + + // Attempt to load the current URL fragment. If a route succeeds with a + // match, returns `true`. If no defined routes matches the fragment, + // returns `false`. + loadUrl: function(fragment) { + // If the root doesn't match, no routes can match either. + if (!this.matchRoot()) return false; + fragment = this.fragment = this.getFragment(fragment); + return _.some(this.handlers, function(handler) { + if (handler.route.test(fragment)) { + handler.callback(fragment); + return true; + } + }); + }, + + // Save a fragment into the hash history, or replace the URL state if the + // 'replace' option is passed. You are responsible for properly URL-encoding + // the fragment in advance. + // + // The options object can contain `trigger: true` if you wish to have the + // route callback be fired (not usually desirable), or `replace: true`, if + // you wish to modify the current URL without adding an entry to the history. + navigate: function(fragment, options) { + if (!History.started) return false; + if (!options || options === true) options = {trigger: !!options}; + + // Normalize the fragment. + fragment = this.getFragment(fragment || ''); + + // Don't include a trailing slash on the root. + var rootPath = this.root; + if (fragment === '' || fragment.charAt(0) === '?') { + rootPath = rootPath.slice(0, -1) || '/'; + } + var url = rootPath + fragment; + + // Strip the hash and decode for matching. + fragment = this.decodeFragment(fragment.replace(pathStripper, '')); + + if (this.fragment === fragment) return; + this.fragment = fragment; + + // If pushState is available, we use it to set the fragment as a real URL. + if (this._usePushState) { + this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url); + + // If hash changes haven't been explicitly disabled, update the hash + // fragment to store history. + } else if (this._wantsHashChange) { + this._updateHash(this.location, fragment, options.replace); + if (this.iframe && fragment !== this.getHash(this.iframe.contentWindow)) { + var iWindow = this.iframe.contentWindow; + + // Opening and closing the iframe tricks IE7 and earlier to push a + // history entry on hash-tag change. When replace is true, we don't + // want this. + if (!options.replace) { + iWindow.document.open(); + iWindow.document.close(); + } + + this._updateHash(iWindow.location, fragment, options.replace); + } + + // If you've told us that you explicitly don't want fallback hashchange- + // based history, then `navigate` becomes a page refresh. + } else { + return this.location.assign(url); + } + if (options.trigger) return this.loadUrl(fragment); + }, + + // Update the hash location, either replacing the current entry, or adding + // a new one to the browser history. + _updateHash: function(location, fragment, replace) { + if (replace) { + var href = location.href.replace(/(javascript:|#).*$/, ''); + location.replace(href + '#' + fragment); + } else { + // Some browsers require that `hash` contains a leading #. + location.hash = '#' + fragment; + } + } + + }); + + // Create the default Backbone.history. + Backbone.history = new History; + + // Helpers + // ------- + + // Helper function to correctly set up the prototype chain for subclasses. + // Similar to `goog.inherits`, but uses a hash of prototype properties and + // class properties to be extended. + var extend = function(protoProps, staticProps) { + var parent = this; + var child; + + // The constructor function for the new subclass is either defined by you + // (the "constructor" property in your `extend` definition), or defaulted + // by us to simply call the parent constructor. + if (protoProps && _.has(protoProps, 'constructor')) { + child = protoProps.constructor; + } else { + child = function(){ return parent.apply(this, arguments); }; + } + + // Add static properties to the constructor function, if supplied. + _.extend(child, parent, staticProps); + + // Set the prototype chain to inherit from `parent`, without calling + // `parent`'s constructor function and add the prototype properties. + child.prototype = _.create(parent.prototype, protoProps); + child.prototype.constructor = child; + + // Set a convenience property in case the parent's prototype is needed + // later. + child.__super__ = parent.prototype; + + return child; + }; + + // Set up inheritance for the model, collection, router, view and history. + Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend; + + // Throw an error when a URL is needed, and none is supplied. + var urlError = function() { + throw new Error('A "url" property or function must be specified'); + }; + + // Wrap an optional error callback with a fallback error event. + var wrapError = function(model, options) { + var error = options.error; + options.error = function(resp) { + if (error) error.call(options.context, model, resp, options); + model.trigger('error', model, resp, options); + }; + }; + + return Backbone; +}); diff --git a/www/js/custom.js b/www/js/custom.js new file mode 100644 index 0000000..92ce726 --- /dev/null +++ b/www/js/custom.js @@ -0,0 +1,204 @@ + +var avsdev = { + pivot: { + dataFile: null, + rowKey: 25, + colKey: 400, + active: false + }, + geoView: { + active: false, + firstRun: true + }, + cc: { +"DZA":"DZA - Algeria","AGO":"AGO - Angola","BEN":"BEN - Benin","BWA":"BWA - Botswana","BFA":"BFA - Burkina Faso","BDI":"BDI - Burundi","CMR":"CMR - Cameroon","CPV":"CPV - Cape Verde","CAF":"CAF - Central African Republic","TCD":"TCD - Chad","COM":"COM - Comoros","COD":"COD - Democratic Republic of the Congo","DJI":"DJI - Djibouti","EGY":"EGY - Egypt","GNQ":"GNQ - Equatorial Guinea","ERI":"ERI - Eritrea","ETH":"ETH - Ethiopia","GAB":"GAB - Gabon","GMB":"GMB - Gambia","GHA":"GHA - Ghana","GIN":"GIN - Guinea","GNB":"GNB - Guinea-Bissau","CIV":"CIV - Côte d'Ivoire","KEN":"KEN - Kenya","LSO":"LSO - Lesotho","LBR":"LBR - Liberia","LBY":"LBY - Libya","MDG":"MDG - Madagascar","MWI":"MWI - Malawi","MLI":"MLI - Mali","MRT":"MRT - Mauritania","MUS":"MUS - Mauritius","MAR":"MAR - Morocco","MOZ":"MOZ - Mozambique","NAM":"NAM - Namibia","NER":"NER - Niger","NGA":"NGA - Nigeria","COG":"COG - Republic of the Congo","RWA":"RWA - Rwanda","SHN":"SHN - Saint Helena","STP":"STP - Sao Tome and Principe","SEN":"SEN - Senegal","SYC":"SYC - Seychelles","SLE":"SLE - Sierra Leone","SOM":"SOM - Somalia","ZAF":"ZAF - South Africa","SSD":"SSD - South Sudan","SDN":"SDN - Sudan","SWZ":"SWZ - Swaziland","TZA":"TZA - Tanzania","TGO":"TGO - Togo","TUN":"TUN - Tunisia","UGA":"UGA - Uganda","ESH":"ESH - Western Sahara","ZMB":"ZMB - Zambia","ZWE":"ZWE - Zimbabwe" + } +}; + +function debounce(func, wait, immediate) { + var timeout; + return function() { + var context = this, args = arguments; + clearTimeout(timeout); + timeout = setTimeout(function() { + timeout = null; + if (!immediate) func.apply(context, args); + }, wait); + if (immediate && !timeout) func.apply(context, args); + }; +} + + +$(document).on('shiny:inputchanged', function(event){ + + if (event.name == 'selectView') { + avsdev.pivot.active = false; + avsdev.geoView.active = false; + + if (event.value == "Geo Image") { + avsdev.geoView.active = true; + if (avsdev.geoView.firstRun) { + zoomGeoView(); + avsdev.geoView.firstRun = false; + } + } + if (event.value == "Pivot Table") { + avsdev.pivot.active = true; + drawPivot(); + } + } + + if (event.name == '.clientdata_output_geoView-mapImage_hidden') { + avsdev.geoView.active = !event.value; + if (avsdev.geoView.firstRun) { + zoomGeoView(); + avsdev.geoView.firstRun = false; + } + } +}); + + + +// GEO + + +_zoomGeoView = function(){ + if (avsdev.geoView.active === false) { + return; + } + + var img = $($("#geoView-mapImage").children("img")[0]); + + if (img.hasClass("zoomed")) { + + if (document.querySelector('#geoView-mapImage img')) { + document.querySelector('#geoView-mapImage img').dispatchEvent(new CustomEvent('wheelzoom.destroy')); + img.removeClass("zoomed"); + } + } + + if (!img.hasClass("zoomed")) { + wheelzoom(img); + img.addClass("zoomed"); + } +}; +zoomGeoView = debounce(function(){ _zoomGeoView() }, 500); + +/* +$(document).on('shiny:sessioninitialized', function(event){ + console.log("shiny:sessioninitialized") + zoomGeoView() +}); +*/ + +$(document).on('shiny:value', function(event){ + if (event.name == 'geoView-mapImage') { + zoomGeoView(); + } +}); + + +// PIVOT + +_drawPivot = function() { + if (avsdev.pivot.active === false) { + return; + } + + $.getJSON(avsdev.pivot.dataFile, function(data) { + data = $(data).each(function(id, v){ + v.Country = avsdev.cc[v.Country]; + }); + data = $.makeArray(data); + + var renderers = $.extend($.pivotUtilities.renderers, $.pivotUtilities.plotly_renderers); + renderers = $.extend(renderers, $.pivotUtilities.avsdev_renderers); + + $("#pivot-pivotRender").pivotUI(data, { + rows: ["Grid Dist."], + cols: ["Pop Density"], + aggregatorName: "Sum", + vals: ["Population"], + renderers: renderers, + rendererName: "Coloured Pivot Table", + hiddenFromDragDrop: ["Population"], + hiddenFromAggregators: ["Grid Dist.", "Country", "Pop Density"], + onRefresh: function(config) { + var config_copy = JSON.parse(JSON.stringify(config)); + //delete some values which are functions + delete config_copy.aggregators; + delete config_copy.renderers; + //delete some bulky default values + delete config_copy.rendererOptions; + delete config_copy.localeStrings; + //console.log(JSON.stringify(config_copy, undefined, 2)); + + var grid = $("td[data-rowkey]").filter(function() { + return $(this).data("rowkey") <= avsdev.pivot.rowKey; + }); + var solar = $("td[data-rowkey]").filter(function() { + return $(this).data("rowkey") > avsdev.pivot.rowKey; + }).filter(function() { + return $(this).data("colkey") <= avsdev.pivot.colKey; + }); + var target = $("td[data-rowkey]").filter(function() { + return $(this).data("rowkey") > avsdev.pivot.rowKey; + }).filter(function() { + return $(this).data("colkey") > avsdev.pivot.colKey; + }); + + grid.addClass("power-grid"); + solar.addClass("power-solar"); + target.addClass("power-target"); + + var gridVal = grid.map(function() { + return $(this).data("value"); + }).get().reduce(function(a, b) { return a + b; }, 0); + + var solarVal = solar.map(function() { + return $(this).data("value"); + }).get().reduce(function(a, b) { return a + b; }, 0); + + var miniVal = target.map(function() { + return $(this).data("value"); + }).get().reduce(function(a, b) { return a + b; }, 0); + + var totalVal = gridVal + solarVal + miniVal; + var offGrid = parseInt($($("#summary-summaryTable table td")[6]).text()) + + $($("#summary-summaryTable table td")[1]).text(Math.round(totalVal - offGrid).toLocaleString('en')); + $($("#summary-summaryTable table td")[2]).text(Math.round(offGrid - solarVal - miniVal).toLocaleString('en')); + $($("#summary-summaryTable table td")[3]).text(Math.round(solarVal).toLocaleString('en')); + $($("#summary-summaryTable table td")[4]).text(Math.round(miniVal).toLocaleString('en')); + $($("#summary-summaryTable table td")[5]).text(Math.round(totalVal).toLocaleString('en')); + } + }); + }); + + //console.log($("td").find(`[data-colKey > 500]`)) + //$("#loading-pivot img").toggleClass("collapse"); + //$("#pivot").toggleClass("collapse"); +}; +drawPivot = debounce(function(){ _drawPivot() }, 750); + +Shiny.addCustomMessageHandler("render-pivot", + function(message) { + avsdev.pivot.dataFile = message.countryTotPath[0] + ".json"; + drawPivot(); + } +); + + +$(document).on('shiny:inputchanged', function(event){ + if (event.name == 'control-selectCountry') { + drawPivot(); + } else if (event.name == 'control-selectGrid') { + avsdev.pivot.rowKey = event.value; + drawPivot(); + } else if (event.name == 'control-selectPopDensity') { + avsdev.pivot.colKey = event.value; + drawPivot(); + } else if (event.name == 'control-selectData') { + drawPivot(); + } +}); diff --git a/www/js/draw.html b/www/js/draw.html new file mode 100644 index 0000000..875c44d --- /dev/null +++ b/www/js/draw.html @@ -0,0 +1,58 @@ +var laydown = { + diagram: { + dataFile: null + } +} + +var graph = new joint.dia.Graph; + +var paper = new joint.dia.Paper({ + el: document.getElementById('myholder'), + model: graph, + width: 600, + height: 100, + gridSize: 1 +}); + +var rect = new joint.shapes.standard.Rectangle(); +var rect2 = rect.clone(); +var link = new joint.shapes.standard.Link(); + +drawDiagram = function() { + rect.position(100, 30); + rect.resize(100, 40); + rect.attr({ + body: { + fill: 'blue' + }, + label: { + text: 'Hello', + fill: 'white' + } + }); + rect.addTo(graph); + + + rect2.translate(300, 0); + rect2.attr('label/text', 'World!'); + rect2.addTo(graph); + + + link.source(rect); + link.target(rect2); + link.addTo(graph); +} + + + +Shiny.addCustomMessageHandler("render-pivot", + function(message) { + laydown.diagram.dataFile = message.laydown + ".json"; + drawDiagram(); + } +); + + +$(document).on('shiny:inputchanged', function(event){ + //TO BE INTEGRATED +}); diff --git a/www/js/draw.js b/www/js/draw.js new file mode 100644 index 0000000..b99daf0 --- /dev/null +++ b/www/js/draw.js @@ -0,0 +1,124 @@ +var dataFile = null; + +addPort = function() { + +} + +drawDiagram = function() { + var graph = new joint.dia.Graph(); + + var paper = new joint.dia.Paper({ + el: document.getElementById('laydown'), + model: graph, + width: 1600, + height: 1000, + gridSize: 1 + }); + + + //Declare an array of rectangles - assign icon, name, + //for each asset in datafile.assets add a rectangle + var row=0; + var col=0; + var space=" "; + var port = { + // id: 'abc', // generated if `id` value is not present + group: 'a', + args: {}, // extra arguments for the port layout function, see `layout.Port` section + label: { + position: { + name: 'right', + args: { y: 6 } // extra arguments for the label layout function, see `layout.PortLabel` section + }, + markup: '' + }, + attrs: { text: { text: 'port1' } }, + //markup: '' + }; + + var assetRects = []; + + dataFile.assets.id.forEach(function(n) { + var rect = new joint.shapes.standard.Rectangle({ + position: {x: (160*col+120), y: (240*row +120) }, + size: {width: 120, height: 80}, + ports: { + groups: { + 'a': { position: 'top'}, + 'b': { position: 'bottom'} + }, + items: [port] + } + }); + + name = dataFile.assets.stencil[n-1].concat(space, dataFile.assets.testRole[n-1]); + //rect.position(240*row, 160*col); + //rect.resize(120, 80); + rect.attr({ + body: { + fill: 'blue' + }, + label: { + text: name, + fill: 'white' + } + }); + assetRects.push(rect); + rect.addTo(graph); + col++; + if (col==5) { + col=0; + row++; + } + }); + + var linkArray = []; + + console.log(dataFile.logConns); + thisObj="Links "; + //Declare an array of links - assign names + dataFile.logConns.id.forEach(function(lc) { + //for (var id in dataFile.logConns) { + fromAsset = dataFile.logConns.fromId[lc-1]; + toAsset= dataFile.logConns.toId[lc-1]; + //console.log(thisObj.concat(lc, " ; ", fromAsset, " : ",toAsset, " : ", fromAsset, " ; ", assetRects[toAsset])); + var link = new joint.shapes.standard.Link(); + link.source(assetRects[fromAsset-1]); + link.target(assetRects[toAsset-1]); + link.router('manhattan'); + link.connector('jumpover'); + linkArray.push(link); + link.addTo(graph); + }); + + paper.options.defaultRouter = { + name: 'manhattan', + args: { + elementPadding: 10 + } + }; + + paper.on('cell:pointerclick', function(cellView) { + cellView.highlight(); + console.log(cellView); + }); + +}; + +Shiny.addCustomMessageHandler("render-laydown", + function(message){ + try { + console.log("Draw diagram"); + dataFile = message; + drawDiagram(); + } catch(e) { + console.log("error object:"); + console.log(e); + } + } +); + + +$(document).on('shiny:inputchanged', function(event){ + //TO BE INTEGRATED +}); diff --git a/www/js/getMouse.js b/www/js/getMouse.js new file mode 100644 index 0000000..a644902 --- /dev/null +++ b/www/js/getMouse.js @@ -0,0 +1,61 @@ +/** + * Retrieve the coordinates of the given event relative to the center +* of the widget. +* + * @param event +* A mouse-related DOM event. +* @param reference +* A DOM element whose position we want to transform the mouse coordinates to. +* @return +* A hash containing keys 'x' and 'y'. +*/ +function getRelativeCoordinates(event, reference) { + var x, y; + event = event || window.event; + var el = event.target || event.srcElement; + console.log(el); + if (!window.opera && typeof event.offsetX != 'undefined') { + // Use offset coordinates and find common offsetParent + var pos = { x: event.offsetX, y: event.offsetY }; + + // Send the coordinates upwards through the offsetParent chain. + var e = el; + while (e) { + e.mouseX = pos.x; + e.mouseY = pos.y; + pos.x += e.offsetLeft; + pos.y += e.offsetTop; + e = e.offsetParent; + } + + // Look for the coordinates starting from the reference element. + var ref = reference; + var offset = { x: 0, y: 0 }; + while (ref) { + if (typeof ref.mouseX != 'undefined') { + x = ref.mouseX - offset.x; + y = ref.mouseY - offset.y; + break; + } + offset.x += ref.offsetLeft; + offset.y += ref.offsetTop; + ref = ref.offsetParent; + } + + // Reset stored coordinates + ref = el; + while (ref) { + ref.mouseX = undefined; + reference.mouseY = undefined; + ref = ref.offsetParent; + } + } + else { + // Use absolute coordinates + var pos = getAbsolutePosition(reference); + x = event.pageX - pos.x; + y = event.pageY - pos.y; + } + // Subtract distance to middle + return { x: x, y: y }; + } diff --git a/www/js/joint.js b/www/js/joint.js new file mode 100644 index 0000000..b0e0c85 --- /dev/null +++ b/www/js/joint.js @@ -0,0 +1,25919 @@ +/*! JointJS v2.1.2 (2018-05-08) - JavaScript diagramming library + + +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ +(function(root, factory) { + + if (typeof define === 'function' && define.amd) { + + // For AMD. + + define(['backbone', 'lodash', 'jquery'], function(Backbone, _, $) { + + Backbone.$ = $; + + return factory(root, Backbone, _, $); + }); + + } else if (typeof exports !== 'undefined') { + + // For Node.js or CommonJS. + + var Backbone = require('backbone'); + var _ = require('lodash'); + var $ = Backbone.$ = require('jquery'); + + module.exports = factory(root, Backbone, _, $); + + } else { + + // As a browser global. + + var Backbone = root.Backbone; + var _ = root._; + var $ = Backbone.$ = root.jQuery || root.$; + + root.joint = factory(root, Backbone, _, $); + root.g = root.joint.g; + root.V = root.Vectorizer = root.joint.V; + } + +}(this, function(root, Backbone, _, $) { + +(function() { + + /** + * version: 0.3.0 + * git://github.com/davidchambers/Base64.js.git + */ + + var object = typeof exports != 'undefined' ? exports : this; // #8: web workers + var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + + function InvalidCharacterError(message) { + this.message = message; + } + + InvalidCharacterError.prototype = new Error; + InvalidCharacterError.prototype.name = 'InvalidCharacterError'; + + // encoder + // [https://gist.github.com/999166] by [https://github.com/nignag] + object.btoa || ( + object.btoa = function(input) { + var str = String(input); + for ( + // initialize result and counter + var block, charCode, idx = 0, map = chars, output = ''; + // if the next str index does not exist: + // change the mapping table to "=" + // check if d has no fractional digits + str.charAt(idx | 0) || (map = '=', idx % 1); + // "8 - idx % 1 * 8" generates the sequence 2, 4, 6, 8 + output += map.charAt(63 & block >> 8 - idx % 1 * 8) + ) { + charCode = str.charCodeAt(idx += 3 / 4); + if (charCode > 0xFF) { + throw new InvalidCharacterError("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range."); + } + block = block << 8 | charCode; + } + return output; + }); + + // decoder + // [https://gist.github.com/1020396] by [https://github.com/atk] + object.atob || ( + object.atob = function(input) { + var str = String(input).replace(/=+$/, ''); + if (str.length % 4 == 1) { + throw new InvalidCharacterError("'atob' failed: The string to be decoded is not correctly encoded."); + } + for ( + // initialize result and counters + var bc = 0, bs, buffer, idx = 0, output = ''; + // get next character + // eslint-disable-next-line no-cond-assign + buffer = str.charAt(idx++); + // character found in table? initialize bit storage and add its ascii value; + ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, + // and if not first of each 4 characters, + // convert the first 8 bits to one ascii character + bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0 + ) { + // try to find character in table (0-63, not found => -1) + buffer = chars.indexOf(buffer); + } + return output; + }); + +}()); + +(function() { + + if (typeof Uint8Array !== 'undefined' || typeof window === 'undefined') { + return; + } + + function subarray(start, end) { + return this.slice(start, end); + } + + function set_(array, offset) { + + if (arguments.length < 2) { + offset = 0; + } + for (var i = 0, n = array.length; i < n; ++i, ++offset) { + this[offset] = array[i] & 0xFF; + } + } + + // we need typed arrays + function TypedArray(arg1) { + + var result; + if (typeof arg1 === 'number') { + result = new Array(arg1); + for (var i = 0; i < arg1; ++i) { + result[i] = 0; + } + } else { + result = arg1.slice(0); + } + result.subarray = subarray; + result.buffer = result; + result.byteLength = result.length; + result.set = set_; + if (typeof arg1 === 'object' && arg1.buffer) { + result.buffer = arg1.buffer; + } + + return result; + } + + window.Uint8Array = TypedArray; + window.Uint32Array = TypedArray; + window.Int32Array = TypedArray; +})(); + +/** + * make xhr.response = 'arraybuffer' available for the IE9 + */ +(function() { + + if (typeof XMLHttpRequest === 'undefined') { + return; + } + + if ('response' in XMLHttpRequest.prototype || + 'mozResponseArrayBuffer' in XMLHttpRequest.prototype || + 'mozResponse' in XMLHttpRequest.prototype || + 'responseArrayBuffer' in XMLHttpRequest.prototype) { + return; + } + + Object.defineProperty(XMLHttpRequest.prototype, 'response', { + get: function() { + return new Uint8Array(new VBArray(this.responseBody).toArray()); + } + }); +})(); + +// https://tc39.github.io/ecma262/#sec-array.prototype.includes +if (!Array.prototype.includes) { + Object.defineProperty(Array.prototype, 'includes', { + value: function(searchElement, fromIndex) { + + // 1. Let O be ? ToObject(this value). + if (this == null) { + throw new TypeError('"this" is null or not defined'); + } + + var o = Object(this); + + // 2. Let len be ? ToLength(? Get(O, "length")). + var len = o.length >>> 0; + + // 3. If len is 0, return false. + if (len === 0) { + return false; + } + + // 4. Let n be ? ToInteger(fromIndex). + // (If fromIndex is undefined, this step produces the value 0.) + var n = fromIndex | 0; + + // 5. If n ≥ 0, then + // a. Let k be n. + // 6. Else n < 0, + // a. Let k be len + n. + // b. If k < 0, let k be 0. + var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); + + function sameValueZero(x, y) { + return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y)); + } + + // 7. Repeat, while k < len + while (k < len) { + // a. Let elementK be the result of ? Get(O, ! ToString(k)). + // b. If SameValueZero(searchElement, elementK) is true, return true. + // c. Increase k by 1. + if (sameValueZero(o[k], searchElement)) { + return true; + } + k++; + } + + // 8. Return false + return false; + } + }); +} + +// https://tc39.github.io/ecma262/#sec-array.prototype.find +if (!Array.prototype.find) { + Object.defineProperty(Array.prototype, 'find', { + value: function(predicate) { + // 1. Let O be ? ToObject(this value). + if (this == null) { + throw new TypeError('"this" is null or not defined'); + } + + var o = Object(this); + + // 2. Let len be ? ToLength(? Get(O, "length")). + var len = o.length >>> 0; + + // 3. If IsCallable(predicate) is false, throw a TypeError exception. + if (typeof predicate !== 'function') { + throw new TypeError('predicate must be a function'); + } + + // 4. If thisArg was supplied, let T be thisArg; else let T be undefined. + var thisArg = arguments[1]; + + // 5. Let k be 0. + var k = 0; + + // 6. Repeat, while k < len + while (k < len) { + // a. Let Pk be ! ToString(k). + // b. Let kValue be ? Get(O, Pk). + // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)). + // d. If testResult is true, return kValue. + var kValue = o[k]; + if (predicate.call(thisArg, kValue, k, o)) { + return kValue; + } + // e. Increase k by 1. + k++; + } + + // 7. Return undefined. + return undefined; + } + }); +} + +// Production steps of ECMA-262, Edition 6, 22.1.2.1 +if (!Array.from) { + Array.from = (function () { + var toStr = Object.prototype.toString; + var isCallable = function (fn) { + return typeof fn === 'function' || toStr.call(fn) === '[object Function]'; + }; + var toInteger = function (value) { + var number = Number(value); + if (isNaN(number)) { return 0; } + if (number === 0 || !isFinite(number)) { return number; } + return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number)); + }; + var maxSafeInteger = Math.pow(2, 53) - 1; + var toLength = function (value) { + var len = toInteger(value); + return Math.min(Math.max(len, 0), maxSafeInteger); + }; + + // The length property of the from method is 1. + return function from(arrayLike/*, mapFn, thisArg */) { + // 1. Let C be the this value. + var C = this; + + // 2. Let items be ToObject(arrayLike). + var items = Object(arrayLike); + + // 3. ReturnIfAbrupt(items). + if (arrayLike == null) { + throw new TypeError('Array.from requires an array-like object - not null or undefined'); + } + + // 4. If mapfn is undefined, then let mapping be false. + var mapFn = arguments.length > 1 ? arguments[1] : void undefined; + var T; + if (typeof mapFn !== 'undefined') { + // 5. else + // 5. a If IsCallable(mapfn) is false, throw a TypeError exception. + if (!isCallable(mapFn)) { + throw new TypeError('Array.from: when provided, the second argument must be a function'); + } + + // 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined. + if (arguments.length > 2) { + T = arguments[2]; + } + } + + // 10. Let lenValue be Get(items, "length"). + // 11. Let len be ToLength(lenValue). + var len = toLength(items.length); + + // 13. If IsConstructor(C) is true, then + // 13. a. Let A be the result of calling the [[Construct]] internal method + // of C with an argument list containing the single item len. + // 14. a. Else, Let A be ArrayCreate(len). + var A = isCallable(C) ? Object(new C(len)) : new Array(len); + + // 16. Let k be 0. + var k = 0; + // 17. Repeat, while k < len… (also steps a - h) + var kValue; + while (k < len) { + kValue = items[k]; + if (mapFn) { + A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k); + } else { + A[k] = kValue; + } + k += 1; + } + // 18. Let putStatus be Put(A, "length", len, true). + A.length = len; + // 20. Return A. + return A; + }; + }()); +} + +// https://tc39.github.io/ecma262/#sec-array.prototype.findIndex +if (!Array.prototype.findIndex) { + Object.defineProperty(Array.prototype, 'findIndex', { + value: function(predicate) { + // 1. Let O be ? ToObject(this value). + if (this == null) { + throw new TypeError('"this" is null or not defined'); + } + + var o = Object(this); + + // 2. Let len be ? ToLength(? Get(O, "length")). + var len = o.length >>> 0; + + // 3. If IsCallable(predicate) is false, throw a TypeError exception. + if (typeof predicate !== 'function') { + throw new TypeError('predicate must be a function'); + } + + // 4. If thisArg was supplied, let T be thisArg; else let T be undefined. + var thisArg = arguments[1]; + + // 5. Let k be 0. + var k = 0; + + // 6. Repeat, while k < len + while (k < len) { + // a. Let Pk be ! ToString(k). + // b. Let kValue be ? Get(O, Pk). + // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)). + // d. If testResult is true, return k. + var kValue = o[k]; + if (predicate.call(thisArg, kValue, k, o)) { + return k; + } + // e. Increase k by 1. + k++; + } + + // 7. Return -1. + return -1; + } + }); +} + +if (!String.prototype.includes) { + String.prototype.includes = function(search, start) { + 'use strict'; + if (typeof start !== 'number') { + start = 0; + } + + if (start + search.length > this.length) { + return false; + } else { + return this.indexOf(search, start) !== -1; + } + }; +} + +if (!String.prototype.startsWith) { + String.prototype.startsWith = function(searchString, position){ + return this.substr(position || 0, searchString.length) === searchString; + }; +} + +Number.isFinite = Number.isFinite || function(value) { + return typeof value === 'number' && isFinite(value); +}; + +//The following works because NaN is the only value in javascript which is not equal to itself. +Number.isNaN = Number.isNaN || function(value) { + return value !== value; +} + + +// Geometry library. +// ----------------- + +var g = (function() { + + var g = {}; + + // Declare shorthands to the most used math functions. + var math = Math; + var abs = math.abs; + var cos = math.cos; + var sin = math.sin; + var sqrt = math.sqrt; + var min = math.min; + var max = math.max; + var atan2 = math.atan2; + var round = math.round; + var floor = math.floor; + var PI = math.PI; + var random = math.random; + var pow = math.pow; + + g.bezier = { + + // Cubic Bezier curve path through points. + // @deprecated + // @param {array} points Array of points through which the smooth line will go. + // @return {array} SVG Path commands as an array + curveThroughPoints: function(points) { + + console.warn('deprecated'); + + return new Path(Curve.throughPoints(points)).serialize(); + }, + + // Get open-ended Bezier Spline Control Points. + // @deprecated + // @param knots Input Knot Bezier spline points (At least two points!). + // @param firstControlPoints Output First Control points. Array of knots.length - 1 length. + // @param secondControlPoints Output Second Control points. Array of knots.length - 1 length. + getCurveControlPoints: function(knots) { + + console.warn('deprecated'); + + var firstControlPoints = []; + var secondControlPoints = []; + var n = knots.length - 1; + var i; + + // Special case: Bezier curve should be a straight line. + if (n == 1) { + // 3P1 = 2P0 + P3 + firstControlPoints[0] = new Point( + (2 * knots[0].x + knots[1].x) / 3, + (2 * knots[0].y + knots[1].y) / 3 + ); + + // P2 = 2P1 – P0 + secondControlPoints[0] = new Point( + 2 * firstControlPoints[0].x - knots[0].x, + 2 * firstControlPoints[0].y - knots[0].y + ); + + return [firstControlPoints, secondControlPoints]; + } + + // Calculate first Bezier control points. + // Right hand side vector. + var rhs = []; + + // Set right hand side X values. + for (i = 1; i < n - 1; i++) { + rhs[i] = 4 * knots[i].x + 2 * knots[i + 1].x; + } + + rhs[0] = knots[0].x + 2 * knots[1].x; + rhs[n - 1] = (8 * knots[n - 1].x + knots[n].x) / 2.0; + + // Get first control points X-values. + var x = this.getFirstControlPoints(rhs); + + // Set right hand side Y values. + for (i = 1; i < n - 1; ++i) { + rhs[i] = 4 * knots[i].y + 2 * knots[i + 1].y; + } + + rhs[0] = knots[0].y + 2 * knots[1].y; + rhs[n - 1] = (8 * knots[n - 1].y + knots[n].y) / 2.0; + + // Get first control points Y-values. + var y = this.getFirstControlPoints(rhs); + + // Fill output arrays. + for (i = 0; i < n; i++) { + // First control point. + firstControlPoints.push(new Point(x[i], y[i])); + + // Second control point. + if (i < n - 1) { + secondControlPoints.push(new Point( + 2 * knots [i + 1].x - x[i + 1], + 2 * knots[i + 1].y - y[i + 1] + )); + + } else { + secondControlPoints.push(new Point( + (knots[n].x + x[n - 1]) / 2, + (knots[n].y + y[n - 1]) / 2) + ); + } + } + + return [firstControlPoints, secondControlPoints]; + }, + + // Solves a tridiagonal system for one of coordinates (x or y) of first Bezier control points. + // @deprecated + // @param rhs Right hand side vector. + // @return Solution vector. + getFirstControlPoints: function(rhs) { + + console.warn('deprecated'); + + var n = rhs.length; + // `x` is a solution vector. + var x = []; + var tmp = []; + var b = 2.0; + + x[0] = rhs[0] / b; + + // Decomposition and forward substitution. + for (var i = 1; i < n; i++) { + tmp[i] = 1 / b; + b = (i < n - 1 ? 4.0 : 3.5) - tmp[i]; + x[i] = (rhs[i] - x[i - 1]) / b; + } + + for (i = 1; i < n; i++) { + // Backsubstitution. + x[n - i - 1] -= tmp[n - i] * x[n - i]; + } + + return x; + }, + + // Divide a Bezier curve into two at point defined by value 't' <0,1>. + // Using deCasteljau algorithm. http://math.stackexchange.com/a/317867 + // @deprecated + // @param control points (start, control start, control end, end) + // @return a function that accepts t and returns 2 curves. + getCurveDivider: function(p0, p1, p2, p3) { + + console.warn('deprecated'); + + var curve = new Curve(p0, p1, p2, p3); + + return function divideCurve(t) { + + var divided = curve.divide(t); + + return [{ + p0: divided[0].start, + p1: divided[0].controlPoint1, + p2: divided[0].controlPoint2, + p3: divided[0].end + }, { + p0: divided[1].start, + p1: divided[1].controlPoint1, + p2: divided[1].controlPoint2, + p3: divided[1].end + }]; + }; + }, + + // Solves an inversion problem -- Given the (x, y) coordinates of a point which lies on + // a parametric curve x = x(t)/w(t), y = y(t)/w(t), find the parameter value t + // which corresponds to that point. + // @deprecated + // @param control points (start, control start, control end, end) + // @return a function that accepts a point and returns t. + getInversionSolver: function(p0, p1, p2, p3) { + + console.warn('deprecated'); + + var curve = new Curve(p0, p1, p2, p3); + + return function solveInversion(p) { + + return curve.closestPointT(p); + }; + } + }; + + var Curve = g.Curve = function(p1, p2, p3, p4) { + + if (!(this instanceof Curve)) { + return new Curve(p1, p2, p3, p4); + } + + if (p1 instanceof Curve) { + return new Curve(p1.start, p1.controlPoint1, p1.controlPoint2, p1.end); + } + + this.start = new Point(p1); + this.controlPoint1 = new Point(p2); + this.controlPoint2 = new Point(p3); + this.end = new Point(p4); + }; + + // Curve passing through points. + // Ported from C# implementation by Oleg V. Polikarpotchkin and Peter Lee (http://www.codeproject.com/KB/graphics/BezierSpline.aspx). + // @param {array} points Array of points through which the smooth line will go. + // @return {array} curves. + Curve.throughPoints = (function() { + + // Solves a tridiagonal system for one of coordinates (x or y) of first Bezier control points. + // @param rhs Right hand side vector. + // @return Solution vector. + function getFirstControlPoints(rhs) { + + var n = rhs.length; + // `x` is a solution vector. + var x = []; + var tmp = []; + var b = 2.0; + + x[0] = rhs[0] / b; + + // Decomposition and forward substitution. + for (var i = 1; i < n; i++) { + tmp[i] = 1 / b; + b = (i < n - 1 ? 4.0 : 3.5) - tmp[i]; + x[i] = (rhs[i] - x[i - 1]) / b; + } + + for (i = 1; i < n; i++) { + // Backsubstitution. + x[n - i - 1] -= tmp[n - i] * x[n - i]; + } + + return x; + } + + // Get open-ended Bezier Spline Control Points. + // @param knots Input Knot Bezier spline points (At least two points!). + // @param firstControlPoints Output First Control points. Array of knots.length - 1 length. + // @param secondControlPoints Output Second Control points. Array of knots.length - 1 length. + function getCurveControlPoints(knots) { + + var firstControlPoints = []; + var secondControlPoints = []; + var n = knots.length - 1; + var i; + + // Special case: Bezier curve should be a straight line. + if (n == 1) { + // 3P1 = 2P0 + P3 + firstControlPoints[0] = new Point( + (2 * knots[0].x + knots[1].x) / 3, + (2 * knots[0].y + knots[1].y) / 3 + ); + + // P2 = 2P1 – P0 + secondControlPoints[0] = new Point( + 2 * firstControlPoints[0].x - knots[0].x, + 2 * firstControlPoints[0].y - knots[0].y + ); + + return [firstControlPoints, secondControlPoints]; + } + + // Calculate first Bezier control points. + // Right hand side vector. + var rhs = []; + + // Set right hand side X values. + for (i = 1; i < n - 1; i++) { + rhs[i] = 4 * knots[i].x + 2 * knots[i + 1].x; + } + + rhs[0] = knots[0].x + 2 * knots[1].x; + rhs[n - 1] = (8 * knots[n - 1].x + knots[n].x) / 2.0; + + // Get first control points X-values. + var x = getFirstControlPoints(rhs); + + // Set right hand side Y values. + for (i = 1; i < n - 1; ++i) { + rhs[i] = 4 * knots[i].y + 2 * knots[i + 1].y; + } + + rhs[0] = knots[0].y + 2 * knots[1].y; + rhs[n - 1] = (8 * knots[n - 1].y + knots[n].y) / 2.0; + + // Get first control points Y-values. + var y = getFirstControlPoints(rhs); + + // Fill output arrays. + for (i = 0; i < n; i++) { + // First control point. + firstControlPoints.push(new Point(x[i], y[i])); + + // Second control point. + if (i < n - 1) { + secondControlPoints.push(new Point( + 2 * knots [i + 1].x - x[i + 1], + 2 * knots[i + 1].y - y[i + 1] + )); + + } else { + secondControlPoints.push(new Point( + (knots[n].x + x[n - 1]) / 2, + (knots[n].y + y[n - 1]) / 2 + )); + } + } + + return [firstControlPoints, secondControlPoints]; + } + + return function(points) { + + if (!points || (Array.isArray(points) && points.length < 2)) { + throw new Error('At least 2 points are required'); + } + + var controlPoints = getCurveControlPoints(points); + + var curves = []; + var n = controlPoints[0].length; + for (var i = 0; i < n; i++) { + + var controlPoint1 = new Point(controlPoints[0][i].x, controlPoints[0][i].y); + var controlPoint2 = new Point(controlPoints[1][i].x, controlPoints[1][i].y); + + curves.push(new Curve(points[i], controlPoint1, controlPoint2, points[i + 1])); + } + + return curves; + }; + })(); + + Curve.prototype = { + + // Returns a bbox that tightly envelops the curve. + bbox: function() { + + var start = this.start; + var controlPoint1 = this.controlPoint1; + var controlPoint2 = this.controlPoint2; + var end = this.end; + + var x0 = start.x; + var y0 = start.y; + var x1 = controlPoint1.x; + var y1 = controlPoint1.y; + var x2 = controlPoint2.x; + var y2 = controlPoint2.y; + var x3 = end.x; + var y3 = end.y; + + var points = new Array(); // local extremes + var tvalues = new Array(); // t values of local extremes + var bounds = [new Array(), new Array()]; + + var a, b, c, t; + var t1, t2; + var b2ac, sqrtb2ac; + + for (var i = 0; i < 2; ++i) { + + if (i === 0) { + b = 6 * x0 - 12 * x1 + 6 * x2; + a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3; + c = 3 * x1 - 3 * x0; + + } else { + b = 6 * y0 - 12 * y1 + 6 * y2; + a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3; + c = 3 * y1 - 3 * y0; + } + + if (abs(a) < 1e-12) { // Numerical robustness + if (abs(b) < 1e-12) { // Numerical robustness + continue; + } + + t = -c / b; + if ((0 < t) && (t < 1)) tvalues.push(t); + + continue; + } + + b2ac = b * b - 4 * c * a; + sqrtb2ac = sqrt(b2ac); + + if (b2ac < 0) continue; + + t1 = (-b + sqrtb2ac) / (2 * a); + if ((0 < t1) && (t1 < 1)) tvalues.push(t1); + + t2 = (-b - sqrtb2ac) / (2 * a); + if ((0 < t2) && (t2 < 1)) tvalues.push(t2); + } + + var j = tvalues.length; + var jlen = j; + var mt; + var x, y; + + while (j--) { + t = tvalues[j]; + mt = 1 - t; + + x = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3); + bounds[0][j] = x; + + y = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3); + bounds[1][j] = y; + + points[j] = { X: x, Y: y }; + } + + tvalues[jlen] = 0; + tvalues[jlen + 1] = 1; + + points[jlen] = { X: x0, Y: y0 }; + points[jlen + 1] = { X: x3, Y: y3 }; + + bounds[0][jlen] = x0; + bounds[1][jlen] = y0; + + bounds[0][jlen + 1] = x3; + bounds[1][jlen + 1] = y3; + + tvalues.length = jlen + 2; + bounds[0].length = jlen + 2; + bounds[1].length = jlen + 2; + points.length = jlen + 2; + + var left = min.apply(null, bounds[0]); + var top = min.apply(null, bounds[1]); + var right = max.apply(null, bounds[0]); + var bottom = max.apply(null, bounds[1]); + + return new Rect(left, top, (right - left), (bottom - top)); + }, + + clone: function() { + + return new Curve(this.start, this.controlPoint1, this.controlPoint2, this.end); + }, + + // Returns the point on the curve closest to point `p` + closestPoint: function(p, opt) { + + return this.pointAtT(this.closestPointT(p, opt)); + }, + + closestPointLength: function(p, opt) { + + opt = opt || {}; + var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; + var subdivisions = (opt.subdivisions === undefined) ? this.getSubdivisions({ precision: precision }) : opt.subdivisions; + var localOpt = { precision: precision, subdivisions: subdivisions }; + + return this.lengthAtT(this.closestPointT(p, localOpt), localOpt); + }, + + closestPointNormalizedLength: function(p, opt) { + + opt = opt || {}; + var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; + var subdivisions = (opt.subdivisions === undefined) ? this.getSubdivisions({ precision: precision }) : opt.subdivisions; + var localOpt = { precision: precision, subdivisions: subdivisions }; + + var cpLength = this.closestPointLength(p, localOpt); + if (!cpLength) return 0; + + var length = this.length(localOpt); + if (length === 0) return 0; + + return cpLength / length; + }, + + // Returns `t` of the point on the curve closest to point `p` + closestPointT: function(p, opt) { + + opt = opt || {}; + var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; + var subdivisions = (opt.subdivisions === undefined) ? this.getSubdivisions({ precision: precision }) : opt.subdivisions; + // does not use localOpt + + // identify the subdivision that contains the point: + var investigatedSubdivision; + var investigatedSubdivisionStartT; // assume that subdivisions are evenly spaced + var investigatedSubdivisionEndT; + var distFromStart; // distance of point from start of baseline + var distFromEnd; // distance of point from end of baseline + var minSumDist; // lowest observed sum of the two distances + var n = subdivisions.length; + var subdivisionSize = (n ? (1 / n) : 0); + for (var i = 0; i < n; i++) { + + var currentSubdivision = subdivisions[i]; + + var startDist = currentSubdivision.start.distance(p); + var endDist = currentSubdivision.end.distance(p); + var sumDist = startDist + endDist; + + // check that the point is closest to current subdivision and not any other + if (!minSumDist || (sumDist < minSumDist)) { + investigatedSubdivision = currentSubdivision; + + investigatedSubdivisionStartT = i * subdivisionSize; + investigatedSubdivisionEndT = (i + 1) * subdivisionSize; + + distFromStart = startDist; + distFromEnd = endDist; + + minSumDist = sumDist; + } + } + + var precisionRatio = pow(10, -precision); + + // recursively divide investigated subdivision: + // until distance between baselinePoint and closest path endpoint is within 10^(-precision) + // then return the closest endpoint of that final subdivision + while (true) { + + // check if we have reached required observed precision + var startPrecisionRatio; + var endPrecisionRatio; + + startPrecisionRatio = (distFromStart ? (abs(distFromStart - distFromEnd) / distFromStart) : 0); + endPrecisionRatio = (distFromEnd ? (abs(distFromStart - distFromEnd) / distFromEnd) : 0); + if ((startPrecisionRatio < precisionRatio) || (endPrecisionRatio) < precisionRatio) { + return ((distFromStart <= distFromEnd) ? investigatedSubdivisionStartT : investigatedSubdivisionEndT); + } + + // otherwise, set up for next iteration + var divided = investigatedSubdivision.divide(0.5); + subdivisionSize /= 2; + + var startDist1 = divided[0].start.distance(p); + var endDist1 = divided[0].end.distance(p); + var sumDist1 = startDist1 + endDist1; + + var startDist2 = divided[1].start.distance(p); + var endDist2 = divided[1].end.distance(p); + var sumDist2 = startDist2 + endDist2; + + if (sumDist1 <= sumDist2) { + investigatedSubdivision = divided[0]; + + investigatedSubdivisionEndT -= subdivisionSize; // subdivisionSize was already halved + + distFromStart = startDist1; + distFromEnd = endDist1; + + } else { + investigatedSubdivision = divided[1]; + + investigatedSubdivisionStartT += subdivisionSize; // subdivisionSize was already halved + + distFromStart = startDist2; + distFromEnd = endDist2; + } + } + }, + + closestPointTangent: function(p, opt) { + + return this.tangentAtT(this.closestPointT(p, opt)); + }, + + // Divides the curve into two at point defined by `t` between 0 and 1. + // Using de Casteljau's algorithm (http://math.stackexchange.com/a/317867). + // Additional resource: https://pomax.github.io/bezierinfo/#decasteljau + divide: function(t) { + + var start = this.start; + var controlPoint1 = this.controlPoint1; + var controlPoint2 = this.controlPoint2; + var end = this.end; + + // shortcuts for `t` values that are out of range + if (t <= 0) { + return [ + new Curve(start, start, start, start), + new Curve(start, controlPoint1, controlPoint2, end) + ]; + } + + if (t >= 1) { + return [ + new Curve(start, controlPoint1, controlPoint2, end), + new Curve(end, end, end, end) + ]; + } + + var dividerPoints = this.getSkeletonPoints(t); + + var startControl1 = dividerPoints.startControlPoint1; + var startControl2 = dividerPoints.startControlPoint2; + var divider = dividerPoints.divider; + var dividerControl1 = dividerPoints.dividerControlPoint1; + var dividerControl2 = dividerPoints.dividerControlPoint2; + + // return array with two new curves + return [ + new Curve(start, startControl1, startControl2, divider), + new Curve(divider, dividerControl1, dividerControl2, end) + ]; + }, + + // Returns the distance between the curve's start and end points. + endpointDistance: function() { + + return this.start.distance(this.end); + }, + + // Checks whether two curves are exactly the same. + equals: function(c) { + + return !!c && + this.start.x === c.start.x && + this.start.y === c.start.y && + this.controlPoint1.x === c.controlPoint1.x && + this.controlPoint1.y === c.controlPoint1.y && + this.controlPoint2.x === c.controlPoint2.x && + this.controlPoint2.y === c.controlPoint2.y && + this.end.x === c.end.x && + this.end.y === c.end.y; + }, + + // Returns five helper points necessary for curve division. + getSkeletonPoints: function(t) { + + var start = this.start; + var control1 = this.controlPoint1; + var control2 = this.controlPoint2; + var end = this.end; + + // shortcuts for `t` values that are out of range + if (t <= 0) { + return { + startControlPoint1: start.clone(), + startControlPoint2: start.clone(), + divider: start.clone(), + dividerControlPoint1: control1.clone(), + dividerControlPoint2: control2.clone() + }; + } + + if (t >= 1) { + return { + startControlPoint1: control1.clone(), + startControlPoint2: control2.clone(), + divider: end.clone(), + dividerControlPoint1: end.clone(), + dividerControlPoint2: end.clone() + }; + } + + var midpoint1 = (new Line(start, control1)).pointAt(t); + var midpoint2 = (new Line(control1, control2)).pointAt(t); + var midpoint3 = (new Line(control2, end)).pointAt(t); + + var subControl1 = (new Line(midpoint1, midpoint2)).pointAt(t); + var subControl2 = (new Line(midpoint2, midpoint3)).pointAt(t); + + var divider = (new Line(subControl1, subControl2)).pointAt(t); + + var output = { + startControlPoint1: midpoint1, + startControlPoint2: subControl1, + divider: divider, + dividerControlPoint1: subControl2, + dividerControlPoint2: midpoint3 + }; + + return output; + }, + + // Returns a list of curves whose flattened length is better than `opt.precision`. + // That is, observed difference in length between recursions is less than 10^(-3) = 0.001 = 0.1% + // (Observed difference is not real precision, but close enough as long as special cases are covered) + // (That is why skipping iteration 1 is important) + // As a rule of thumb, increasing `precision` by 1 requires two more division operations + // - Precision 0 (endpointDistance) - total of 2^0 - 1 = 0 operations (1 subdivision) + // - Precision 1 (<10% error) - total of 2^2 - 1 = 3 operations (4 subdivisions) + // - Precision 2 (<1% error) - total of 2^4 - 1 = 15 operations requires 4 division operations on all elements (15 operations total) (16 subdivisions) + // - Precision 3 (<0.1% error) - total of 2^6 - 1 = 63 operations - acceptable when drawing (64 subdivisions) + // - Precision 4 (<0.01% error) - total of 2^8 - 1 = 255 operations - high resolution, can be used to interpolate `t` (256 subdivisions) + // (Variation of 1 recursion worse or better is possible depending on the curve, doubling/halving the number of operations accordingly) + getSubdivisions: function(opt) { + + opt = opt || {}; + var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; + // not using opt.subdivisions + // not using localOpt + + var subdivisions = [new Curve(this.start, this.controlPoint1, this.controlPoint2, this.end)]; + if (precision === 0) return subdivisions; + + var previousLength = this.endpointDistance(); + + var precisionRatio = pow(10, -precision); + + // recursively divide curve at `t = 0.5` + // until the difference between observed length at subsequent iterations is lower than precision + var iteration = 0; + while (true) { + iteration += 1; + + // divide all subdivisions + var newSubdivisions = []; + var numSubdivisions = subdivisions.length; + for (var i = 0; i < numSubdivisions; i++) { + + var currentSubdivision = subdivisions[i]; + var divided = currentSubdivision.divide(0.5); // dividing at t = 0.5 (not at middle length!) + newSubdivisions.push(divided[0], divided[1]); + } + + // measure new length + var length = 0; + var numNewSubdivisions = newSubdivisions.length; + for (var j = 0; j < numNewSubdivisions; j++) { + + var currentNewSubdivision = newSubdivisions[j]; + length += currentNewSubdivision.endpointDistance(); + } + + // check if we have reached required observed precision + // sine-like curves may have the same observed length in iteration 0 and 1 - skip iteration 1 + // not a problem for further iterations because cubic curves cannot have more than two local extrema + // (i.e. cubic curves cannot intersect the baseline more than once) + // therefore two subsequent iterations cannot produce sampling with equal length + var observedPrecisionRatio = ((length !== 0) ? ((length - previousLength) / length) : 0); + if (iteration > 1 && observedPrecisionRatio < precisionRatio) { + return newSubdivisions; + } + + // otherwise, set up for next iteration + subdivisions = newSubdivisions; + previousLength = length; + } + }, + + isDifferentiable: function() { + + var start = this.start; + var control1 = this.controlPoint1; + var control2 = this.controlPoint2; + var end = this.end; + + return !(start.equals(control1) && control1.equals(control2) && control2.equals(end)); + }, + + // Returns flattened length of the curve with precision better than `opt.precision`; or using `opt.subdivisions` provided. + length: function(opt) { + + opt = opt || {}; + var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; // opt.precision only used in getSubdivisions() call + var subdivisions = (opt.subdivisions === undefined) ? this.getSubdivisions({ precision: precision }) : opt.subdivisions; + // not using localOpt + + var length = 0; + var n = subdivisions.length; + for (var i = 0; i < n; i++) { + + var currentSubdivision = subdivisions[i]; + length += currentSubdivision.endpointDistance(); + } + + return length; + }, + + // Returns distance along the curve up to `t` with precision better than requested `opt.precision`. (Not using `opt.subdivisions`.) + lengthAtT: function(t, opt) { + + if (t <= 0) return 0; + + opt = opt || {}; + var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; + // not using opt.subdivisions + // not using localOpt + + var subCurve = this.divide(t)[0]; + var subCurveLength = subCurve.length({ precision: precision }); + + return subCurveLength; + }, + + // Returns point at requested `ratio` between 0 and 1 with precision better than `opt.precision`; optionally using `opt.subdivisions` provided. + // Mirrors Line.pointAt() function. + // For a function that tracks `t`, use Curve.pointAtT(). + pointAt: function(ratio, opt) { + + if (ratio <= 0) return this.start.clone(); + if (ratio >= 1) return this.end.clone(); + + var t = this.tAt(ratio, opt); + + return this.pointAtT(t); + }, + + // Returns point at requested `length` with precision better than requested `opt.precision`; optionally using `opt.subdivisions` provided. + pointAtLength: function(length, opt) { + + var t = this.tAtLength(length, opt); + + return this.pointAtT(t); + }, + + // Returns the point at provided `t` between 0 and 1. + // `t` does not track distance along curve as it does in Line objects. + // Non-linear relationship, speeds up and slows down as curve warps! + // For linear length-based solution, use Curve.pointAt(). + pointAtT: function(t) { + + if (t <= 0) return this.start.clone(); + if (t >= 1) return this.end.clone(); + + return this.getSkeletonPoints(t).divider; + }, + + // Default precision + PRECISION: 3, + + scale: function(sx, sy, origin) { + + this.start.scale(sx, sy, origin); + this.controlPoint1.scale(sx, sy, origin); + this.controlPoint2.scale(sx, sy, origin); + this.end.scale(sx, sy, origin); + return this; + }, + + // Returns a tangent line at requested `ratio` with precision better than requested `opt.precision`; or using `opt.subdivisions` provided. + tangentAt: function(ratio, opt) { + + if (!this.isDifferentiable()) return null; + + if (ratio < 0) ratio = 0; + else if (ratio > 1) ratio = 1; + + var t = this.tAt(ratio, opt); + + return this.tangentAtT(t); + }, + + // Returns a tangent line at requested `length` with precision better than requested `opt.precision`; or using `opt.subdivisions` provided. + tangentAtLength: function(length, opt) { + + if (!this.isDifferentiable()) return null; + + var t = this.tAtLength(length, opt); + + return this.tangentAtT(t); + }, + + // Returns a tangent line at requested `t`. + tangentAtT: function(t) { + + if (!this.isDifferentiable()) return null; + + if (t < 0) t = 0; + else if (t > 1) t = 1; + + var skeletonPoints = this.getSkeletonPoints(t); + + var p1 = skeletonPoints.startControlPoint2; + var p2 = skeletonPoints.dividerControlPoint1; + + var tangentStart = skeletonPoints.divider; + + var tangentLine = new Line(p1, p2); + tangentLine.translate(tangentStart.x - p1.x, tangentStart.y - p1.y); // move so that tangent line starts at the point requested + + return tangentLine; + }, + + // Returns `t` at requested `ratio` with precision better than requested `opt.precision`; optionally using `opt.subdivisions` provided. + tAt: function(ratio, opt) { + + if (ratio <= 0) return 0; + if (ratio >= 1) return 1; + + opt = opt || {}; + var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; + var subdivisions = (opt.subdivisions === undefined) ? this.getSubdivisions({ precision: precision }) : opt.subdivisions; + var localOpt = { precision: precision, subdivisions: subdivisions }; + + var curveLength = this.length(localOpt); + var length = curveLength * ratio; + + return this.tAtLength(length, localOpt); + }, + + // Returns `t` at requested `length` with precision better than requested `opt.precision`; optionally using `opt.subdivisions` provided. + // Uses `precision` to approximate length within `precision` (always underestimates) + // Then uses a binary search to find the `t` of a subdivision endpoint that is close (within `precision`) to the `length`, if the curve was as long as approximated + // As a rule of thumb, increasing `precision` by 1 causes the algorithm to go 2^(precision - 1) deeper + // - Precision 0 (chooses one of the two endpoints) - 0 levels + // - Precision 1 (chooses one of 5 points, <10% error) - 1 level + // - Precision 2 (<1% error) - 3 levels + // - Precision 3 (<0.1% error) - 7 levels + // - Precision 4 (<0.01% error) - 15 levels + tAtLength: function(length, opt) { + + var fromStart = true; + if (length < 0) { + fromStart = false; // negative lengths mean start calculation from end point + length = -length; // absolute value + } + + opt = opt || {}; + var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; + var subdivisions = (opt.subdivisions === undefined) ? this.getSubdivisions({ precision: precision }) : opt.subdivisions; + var localOpt = { precision: precision, subdivisions: subdivisions }; + + // identify the subdivision that contains the point at requested `length`: + var investigatedSubdivision; + var investigatedSubdivisionStartT; // assume that subdivisions are evenly spaced + var investigatedSubdivisionEndT; + //var baseline; // straightened version of subdivision to investigate + //var baselinePoint; // point on the baseline that is the requested distance away from start + var baselinePointDistFromStart; // distance of baselinePoint from start of baseline + var baselinePointDistFromEnd; // distance of baselinePoint from end of baseline + var l = 0; // length so far + var n = subdivisions.length; + var subdivisionSize = 1 / n; + for (var i = (fromStart ? (0) : (n - 1)); (fromStart ? (i < n) : (i >= 0)); (fromStart ? (i++) : (i--))) { + + var currentSubdivision = subdivisions[i]; + var d = currentSubdivision.endpointDistance(); // length of current subdivision + + if (length <= (l + d)) { + investigatedSubdivision = currentSubdivision; + + investigatedSubdivisionStartT = i * subdivisionSize; + investigatedSubdivisionEndT = (i + 1) * subdivisionSize; + + baselinePointDistFromStart = (fromStart ? (length - l) : ((d + l) - length)); + baselinePointDistFromEnd = (fromStart ? ((d + l) - length) : (length - l)); + + break; + } + + l += d; + } + + if (!investigatedSubdivision) return (fromStart ? 1 : 0); // length requested is out of range - return maximum t + // note that precision affects what length is recorded + // (imprecise measurements underestimate length by up to 10^(-precision) of the precise length) + // e.g. at precision 1, the length may be underestimated by up to 10% and cause this function to return 1 + + var curveLength = this.length(localOpt); + + var precisionRatio = pow(10, -precision); + + // recursively divide investigated subdivision: + // until distance between baselinePoint and closest path endpoint is within 10^(-precision) + // then return the closest endpoint of that final subdivision + while (true) { + + // check if we have reached required observed precision + var observedPrecisionRatio; + + observedPrecisionRatio = ((curveLength !== 0) ? (baselinePointDistFromStart / curveLength) : 0); + if (observedPrecisionRatio < precisionRatio) return investigatedSubdivisionStartT; + observedPrecisionRatio = ((curveLength !== 0) ? (baselinePointDistFromEnd / curveLength) : 0); + if (observedPrecisionRatio < precisionRatio) return investigatedSubdivisionEndT; + + // otherwise, set up for next iteration + var newBaselinePointDistFromStart; + var newBaselinePointDistFromEnd; + + var divided = investigatedSubdivision.divide(0.5); + subdivisionSize /= 2; + + var baseline1Length = divided[0].endpointDistance(); + var baseline2Length = divided[1].endpointDistance(); + + if (baselinePointDistFromStart <= baseline1Length) { // point at requested length is inside divided[0] + investigatedSubdivision = divided[0]; + + investigatedSubdivisionEndT -= subdivisionSize; // sudivisionSize was already halved + + newBaselinePointDistFromStart = baselinePointDistFromStart; + newBaselinePointDistFromEnd = baseline1Length - newBaselinePointDistFromStart; + + } else { // point at requested length is inside divided[1] + investigatedSubdivision = divided[1]; + + investigatedSubdivisionStartT += subdivisionSize; // subdivisionSize was already halved + + newBaselinePointDistFromStart = baselinePointDistFromStart - baseline1Length; + newBaselinePointDistFromEnd = baseline2Length - newBaselinePointDistFromStart; + } + + baselinePointDistFromStart = newBaselinePointDistFromStart; + baselinePointDistFromEnd = newBaselinePointDistFromEnd; + } + }, + + translate: function(tx, ty) { + + this.start.translate(tx, ty); + this.controlPoint1.translate(tx, ty); + this.controlPoint2.translate(tx, ty); + this.end.translate(tx, ty); + return this; + }, + + // Returns an array of points that represents the curve when flattened, up to `opt.precision`; or using `opt.subdivisions` provided. + // Flattened length is no more than 10^(-precision) away from real curve length. + toPoints: function(opt) { + + opt = opt || {}; + var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; // opt.precision only used in getSubdivisions() call + var subdivisions = (opt.subdivisions === undefined) ? this.getSubdivisions({ precision: precision }) : opt.subdivisions; + // not using localOpt + + var points = [subdivisions[0].start.clone()]; + var n = subdivisions.length; + for (var i = 0; i < n; i++) { + + var currentSubdivision = subdivisions[i]; + points.push(currentSubdivision.end.clone()); + } + + return points; + }, + + // Returns a polyline that represents the curve when flattened, up to `opt.precision`; or using `opt.subdivisions` provided. + // Flattened length is no more than 10^(-precision) away from real curve length. + toPolyline: function(opt) { + + return new Polyline(this.toPoints(opt)); + }, + + toString: function() { + + return this.start + ' ' + this.controlPoint1 + ' ' + this.controlPoint2 + ' ' + this.end; + } + }; + + var Ellipse = g.Ellipse = function(c, a, b) { + + if (!(this instanceof Ellipse)) { + return new Ellipse(c, a, b); + } + + if (c instanceof Ellipse) { + return new Ellipse(new Point(c.x, c.y), c.a, c.b); + } + + c = new Point(c); + this.x = c.x; + this.y = c.y; + this.a = a; + this.b = b; + }; + + Ellipse.fromRect = function(rect) { + + rect = new Rect(rect); + return new Ellipse(rect.center(), rect.width / 2, rect.height / 2); + }; + + Ellipse.prototype = { + + bbox: function() { + + return new Rect(this.x - this.a, this.y - this.b, 2 * this.a, 2 * this.b); + }, + + clone: function() { + + return new Ellipse(this); + }, + + /** + * @param {g.Point} point + * @returns {number} result < 1 - inside ellipse, result == 1 - on ellipse boundary, result > 1 - outside + */ + normalizedDistance: function(point) { + + var x0 = point.x; + var y0 = point.y; + var a = this.a; + var b = this.b; + var x = this.x; + var y = this.y; + + return ((x0 - x) * (x0 - x)) / (a * a ) + ((y0 - y) * (y0 - y)) / (b * b); + }, + + // inflate by dx and dy + // @param dx {delta_x} representing additional size to x + // @param dy {delta_y} representing additional size to y - + // dy param is not required -> in that case y is sized by dx + inflate: function(dx, dy) { + if (dx === undefined) { + dx = 0; + } + + if (dy === undefined) { + dy = dx; + } + + this.a += 2 * dx; + this.b += 2 * dy; + + return this; + }, + + + /** + * @param {g.Point} p + * @returns {boolean} + */ + containsPoint: function(p) { + + return this.normalizedDistance(p) <= 1; + }, + + /** + * @returns {g.Point} + */ + center: function() { + + return new Point(this.x, this.y); + }, + + /** Compute angle between tangent and x axis + * @param {g.Point} p Point of tangency, it has to be on ellipse boundaries. + * @returns {number} angle between tangent and x axis + */ + tangentTheta: function(p) { + + var refPointDelta = 30; + var x0 = p.x; + var y0 = p.y; + var a = this.a; + var b = this.b; + var center = this.bbox().center(); + var m = center.x; + var n = center.y; + + var q1 = x0 > center.x + a / 2; + var q3 = x0 < center.x - a / 2; + + var y, x; + if (q1 || q3) { + y = x0 > center.x ? y0 - refPointDelta : y0 + refPointDelta; + x = (a * a / (x0 - m)) - (a * a * (y0 - n) * (y - n)) / (b * b * (x0 - m)) + m; + + } else { + x = y0 > center.y ? x0 + refPointDelta : x0 - refPointDelta; + y = ( b * b / (y0 - n)) - (b * b * (x0 - m) * (x - m)) / (a * a * (y0 - n)) + n; + } + + return (new Point(x, y)).theta(p); + + }, + + equals: function(ellipse) { + + return !!ellipse && + ellipse.x === this.x && + ellipse.y === this.y && + ellipse.a === this.a && + ellipse.b === this.b; + }, + + intersectionWithLine: function(line) { + + var intersections = []; + var a1 = line.start; + var a2 = line.end; + var rx = this.a; + var ry = this.b; + var dir = line.vector(); + var diff = a1.difference(new Point(this)); + var mDir = new Point(dir.x / (rx * rx), dir.y / (ry * ry)); + var mDiff = new Point(diff.x / (rx * rx), diff.y / (ry * ry)); + + var a = dir.dot(mDir); + var b = dir.dot(mDiff); + var c = diff.dot(mDiff) - 1.0; + var d = b * b - a * c; + + if (d < 0) { + return null; + } else if (d > 0) { + var root = sqrt(d); + var ta = (-b - root) / a; + var tb = (-b + root) / a; + + if ((ta < 0 || 1 < ta) && (tb < 0 || 1 < tb)) { + // if ((ta < 0 && tb < 0) || (ta > 1 && tb > 1)) outside else inside + return null; + } else { + if (0 <= ta && ta <= 1) intersections.push(a1.lerp(a2, ta)); + if (0 <= tb && tb <= 1) intersections.push(a1.lerp(a2, tb)); + } + } else { + var t = -b / a; + if (0 <= t && t <= 1) { + intersections.push(a1.lerp(a2, t)); + } else { + // outside + return null; + } + } + + return intersections; + }, + + // Find point on me where line from my center to + // point p intersects my boundary. + // @param {number} angle If angle is specified, intersection with rotated ellipse is computed. + intersectionWithLineFromCenterToPoint: function(p, angle) { + + p = new Point(p); + + if (angle) p.rotate(new Point(this.x, this.y), angle); + + var dx = p.x - this.x; + var dy = p.y - this.y; + var result; + + if (dx === 0) { + result = this.bbox().pointNearestToPoint(p); + if (angle) return result.rotate(new Point(this.x, this.y), -angle); + return result; + } + + var m = dy / dx; + var mSquared = m * m; + var aSquared = this.a * this.a; + var bSquared = this.b * this.b; + + var x = sqrt(1 / ((1 / aSquared) + (mSquared / bSquared))); + x = dx < 0 ? -x : x; + + var y = m * x; + result = new Point(this.x + x, this.y + y); + + if (angle) return result.rotate(new Point(this.x, this.y), -angle); + return result; + }, + + toString: function() { + + return (new Point(this.x, this.y)).toString() + ' ' + this.a + ' ' + this.b; + } + }; + + var Line = g.Line = function(p1, p2) { + + if (!(this instanceof Line)) { + return new Line(p1, p2); + } + + if (p1 instanceof Line) { + return new Line(p1.start, p1.end); + } + + this.start = new Point(p1); + this.end = new Point(p2); + }; + + Line.prototype = { + + bbox: function() { + + var left = min(this.start.x, this.end.x); + var top = min(this.start.y, this.end.y); + var right = max(this.start.x, this.end.x); + var bottom = max(this.start.y, this.end.y); + + return new Rect(left, top, (right - left), (bottom - top)); + }, + + // @return the bearing (cardinal direction) of the line. For example N, W, or SE. + // @returns {String} One of the following bearings : NE, E, SE, S, SW, W, NW, N. + bearing: function() { + + var lat1 = toRad(this.start.y); + var lat2 = toRad(this.end.y); + var lon1 = this.start.x; + var lon2 = this.end.x; + var dLon = toRad(lon2 - lon1); + var y = sin(dLon) * cos(lat2); + var x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon); + var brng = toDeg(atan2(y, x)); + + var bearings = ['NE', 'E', 'SE', 'S', 'SW', 'W', 'NW', 'N']; + + var index = brng - 22.5; + if (index < 0) + index += 360; + index = parseInt(index / 45); + + return bearings[index]; + }, + + clone: function() { + + return new Line(this.start, this.end); + }, + + // @return {point} the closest point on the line to point `p` + closestPoint: function(p) { + + return this.pointAt(this.closestPointNormalizedLength(p)); + }, + + closestPointLength: function(p) { + + return this.closestPointNormalizedLength(p) * this.length(); + }, + + // @return {number} the normalized length of the closest point on the line to point `p` + closestPointNormalizedLength: function(p) { + + var product = this.vector().dot((new Line(this.start, p)).vector()); + var cpNormalizedLength = min(1, max(0, product / this.squaredLength())); + + // cpNormalizedLength returns `NaN` if this line has zero length + // we can work with that - if `NaN`, return 0 + if (cpNormalizedLength !== cpNormalizedLength) return 0; // condition evaluates to `true` if and only if cpNormalizedLength is `NaN` + // (`NaN` is the only value that is not equal to itself) + + return cpNormalizedLength; + }, + + closestPointTangent: function(p) { + + return this.tangentAt(this.closestPointNormalizedLength(p)); + }, + + equals: function(l) { + + return !!l && + this.start.x === l.start.x && + this.start.y === l.start.y && + this.end.x === l.end.x && + this.end.y === l.end.y; + }, + + intersectionWithLine: function(line) { + + var pt1Dir = new Point(this.end.x - this.start.x, this.end.y - this.start.y); + var pt2Dir = new Point(line.end.x - line.start.x, line.end.y - line.start.y); + var det = (pt1Dir.x * pt2Dir.y) - (pt1Dir.y * pt2Dir.x); + var deltaPt = new Point(line.start.x - this.start.x, line.start.y - this.start.y); + var alpha = (deltaPt.x * pt2Dir.y) - (deltaPt.y * pt2Dir.x); + var beta = (deltaPt.x * pt1Dir.y) - (deltaPt.y * pt1Dir.x); + + if (det === 0 || alpha * det < 0 || beta * det < 0) { + // No intersection found. + return null; + } + + if (det > 0) { + if (alpha > det || beta > det) { + return null; + } + + } else { + if (alpha < det || beta < det) { + return null; + } + } + + return [new Point( + this.start.x + (alpha * pt1Dir.x / det), + this.start.y + (alpha * pt1Dir.y / det) + )]; + }, + + // @return {point} Point where I'm intersecting a line. + // @return [point] Points where I'm intersecting a rectangle. + // @see Squeak Smalltalk, LineSegment>>intersectionWith: + intersect: function(shape, opt) { + + if (shape instanceof Line || + shape instanceof Rect || + shape instanceof Polyline || + shape instanceof Ellipse || + shape instanceof Path + ) { + var intersection = shape.intersectionWithLine(this, opt); + + // Backwards compatibility + if (intersection && (shape instanceof Line)) { + intersection = intersection[0]; + } + + return intersection; + } + + return null; + }, + + isDifferentiable: function() { + + return !this.start.equals(this.end); + }, + + // @return {double} length of the line + length: function() { + + return sqrt(this.squaredLength()); + }, + + // @return {point} my midpoint + midpoint: function() { + + return new Point( + (this.start.x + this.end.x) / 2, + (this.start.y + this.end.y) / 2 + ); + }, + + // @return {point} my point at 't' <0,1> + pointAt: function(t) { + + var start = this.start; + var end = this.end; + + if (t <= 0) return start.clone(); + if (t >= 1) return end.clone(); + + return start.lerp(end, t); + }, + + pointAtLength: function(length) { + + var start = this.start; + var end = this.end; + + var fromStart = true; + if (length < 0) { + fromStart = false; // negative lengths mean start calculation from end point + length = -length; // absolute value + } + + var lineLength = this.length(); + if (length >= lineLength) return (fromStart ? end.clone() : start.clone()); + + return this.pointAt((fromStart ? (length) : (lineLength - length)) / lineLength); + }, + + // @return {number} the offset of the point `p` from the line. + if the point `p` is on the right side of the line, - if on the left and 0 if on the line. + pointOffset: function(p) { + + // Find the sign of the determinant of vectors (start,end), where p is the query point. + p = new g.Point(p); + var start = this.start; + var end = this.end; + var determinant = ((end.x - start.x) * (p.y - start.y) - (end.y - start.y) * (p.x - start.x)); + + return determinant / this.length(); + }, + + rotate: function(origin, angle) { + + this.start.rotate(origin, angle); + this.end.rotate(origin, angle); + return this; + }, + + round: function(precision) { + + var f = pow(10, precision || 0); + this.start.x = round(this.start.x * f) / f; + this.start.y = round(this.start.y * f) / f; + this.end.x = round(this.end.x * f) / f; + this.end.y = round(this.end.y * f) / f; + return this; + }, + + scale: function(sx, sy, origin) { + + this.start.scale(sx, sy, origin); + this.end.scale(sx, sy, origin); + return this; + }, + + // @return {number} scale the line so that it has the requested length + setLength: function(length) { + + var currentLength = this.length(); + if (!currentLength) return this; + + var scaleFactor = length / currentLength; + return this.scale(scaleFactor, scaleFactor, this.start); + }, + + // @return {integer} length without sqrt + // @note for applications where the exact length is not necessary (e.g. compare only) + squaredLength: function() { + + var x0 = this.start.x; + var y0 = this.start.y; + var x1 = this.end.x; + var y1 = this.end.y; + return (x0 -= x1) * x0 + (y0 -= y1) * y0; + }, + + tangentAt: function(t) { + + if (!this.isDifferentiable()) return null; + + var start = this.start; + var end = this.end; + + var tangentStart = this.pointAt(t); // constrains `t` between 0 and 1 + + var tangentLine = new Line(start, end); + tangentLine.translate(tangentStart.x - start.x, tangentStart.y - start.y); // move so that tangent line starts at the point requested + + return tangentLine; + }, + + tangentAtLength: function(length) { + + if (!this.isDifferentiable()) return null; + + var start = this.start; + var end = this.end; + + var tangentStart = this.pointAtLength(length); + + var tangentLine = new Line(start, end); + tangentLine.translate(tangentStart.x - start.x, tangentStart.y - start.y); // move so that tangent line starts at the point requested + + return tangentLine; + }, + + translate: function(tx, ty) { + + this.start.translate(tx, ty); + this.end.translate(tx, ty); + return this; + }, + + // @return vector {point} of the line + vector: function() { + + return new Point(this.end.x - this.start.x, this.end.y - this.start.y); + }, + + toString: function() { + + return this.start.toString() + ' ' + this.end.toString(); + } + }; + + // For backwards compatibility: + Line.prototype.intersection = Line.prototype.intersect; + + // Accepts path data string, array of segments, array of Curves and/or Lines, or a Polyline. + // Path created is not guaranteed to be a valid (serializable) path (might not start with an M). + var Path = g.Path = function(arg) { + + if (!(this instanceof Path)) { + return new Path(arg); + } + + if (typeof arg === 'string') { // create from a path data string + return new Path.parse(arg); + } + + this.segments = []; + + var i; + var n; + + if (!arg) { + // don't do anything + + } else if (Array.isArray(arg) && arg.length !== 0) { // if arg is a non-empty array + n = arg.length; + if (arg[0].isSegment) { // create from an array of segments + for (i = 0; i < n; i++) { + + var segment = arg[i]; + + this.appendSegment(segment); + } + + } else { // create from an array of Curves and/or Lines + var previousObj = null; + for (i = 0; i < n; i++) { + + var obj = arg[i]; + + if (!((obj instanceof Line) || (obj instanceof Curve))) { + throw new Error('Cannot construct a path segment from the provided object.'); + } + + if (i === 0) this.appendSegment(Path.createSegment('M', obj.start)); + + // if objects do not link up, moveto segments are inserted to cover the gaps + if (previousObj && !previousObj.end.equals(obj.start)) this.appendSegment(Path.createSegment('M', obj.start)); + + if (obj instanceof Line) { + this.appendSegment(Path.createSegment('L', obj.end)); + + } else if (obj instanceof Curve) { + this.appendSegment(Path.createSegment('C', obj.controlPoint1, obj.controlPoint2, obj.end)); + } + + previousObj = obj; + } + } + + } else if (arg.isSegment) { // create from a single segment + this.appendSegment(arg); + + } else if (arg instanceof Line) { // create from a single Line + this.appendSegment(Path.createSegment('M', arg.start)); + this.appendSegment(Path.createSegment('L', arg.end)); + + } else if (arg instanceof Curve) { // create from a single Curve + this.appendSegment(Path.createSegment('M', arg.start)); + this.appendSegment(Path.createSegment('C', arg.controlPoint1, arg.controlPoint2, arg.end)); + + } else if (arg instanceof Polyline && arg.points && arg.points.length !== 0) { // create from a Polyline + n = arg.points.length; + for (i = 0; i < n; i++) { + + var point = arg.points[i]; + + if (i === 0) this.appendSegment(Path.createSegment('M', point)); + else this.appendSegment(Path.createSegment('L', point)); + } + } + }; + + // More permissive than V.normalizePathData and Path.prototype.serialize. + // Allows path data strings that do not start with a Moveto command (unlike SVG specification). + // Does not require spaces between elements; commas are allowed, separators may be omitted when unambiguous (e.g. 'ZM10,10', 'L1.6.8', 'M100-200'). + // Allows for command argument chaining. + // Throws an error if wrong number of arguments is provided with a command. + // Throws an error if an unrecognized path command is provided (according to Path.segmentTypes). Only a subset of SVG commands is currently supported (L, C, M, Z). + Path.parse = function(pathData) { + + if (!pathData) return new Path(); + + var path = new Path(); + + var commandRe = /(?:[a-zA-Z] *)(?:(?:-?\d+(?:\.\d+)? *,? *)|(?:-?\.\d+ *,? *))+|(?:[a-zA-Z] *)(?! |\d|-|\.)/g; + var commands = pathData.match(commandRe); + + var numCommands = commands.length; + for (var i = 0; i < numCommands; i++) { + + var command = commands[i]; + var argRe = /(?:[a-zA-Z])|(?:(?:-?\d+(?:\.\d+)?))|(?:(?:-?\.\d+))/g; + var args = command.match(argRe); + + var segment = Path.createSegment.apply(this, args); // args = [type, coordinate1, coordinate2...] + path.appendSegment(segment); + } + + return path; + }; + + // Create a segment or an array of segments. + // Accepts unlimited points/coords arguments after `type`. + Path.createSegment = function(type) { + + if (!type) throw new Error('Type must be provided.'); + + var segmentConstructor = Path.segmentTypes[type]; + if (!segmentConstructor) throw new Error(type + ' is not a recognized path segment type.'); + + var args = []; + var n = arguments.length; + for (var i = 1; i < n; i++) { // do not add first element (`type`) to args array + args.push(arguments[i]); + } + + return applyToNew(segmentConstructor, args); + }, + + Path.prototype = { + + // Accepts one segment or an array of segments as argument. + // Throws an error if argument is not a segment or an array of segments. + appendSegment: function(arg) { + + var segments = this.segments; + var numSegments = segments.length; + // works even if path has no segments + + var currentSegment; + + var previousSegment = ((numSegments !== 0) ? segments[numSegments - 1] : null); // if we are appending to an empty path, previousSegment is null + var nextSegment = null; + + if (!Array.isArray(arg)) { // arg is a segment + if (!arg || !arg.isSegment) throw new Error('Segment required.'); + + currentSegment = this.prepareSegment(arg, previousSegment, nextSegment); + segments.push(currentSegment); + + } else { // arg is an array of segments + if (!arg[0].isSegment) throw new Error('Segments required.'); + + var n = arg.length; + for (var i = 0; i < n; i++) { + + var currentArg = arg[i]; + currentSegment = this.prepareSegment(currentArg, previousSegment, nextSegment); + segments.push(currentSegment); + previousSegment = currentSegment; + } + } + }, + + // Returns the bbox of the path. + // If path has no segments, returns null. + // If path has only invisible segments, returns bbox of the end point of last segment. + bbox: function() { + + var segments = this.segments; + var numSegments = segments.length; + if (numSegments === 0) return null; // if segments is an empty array + + var bbox; + for (var i = 0; i < numSegments; i++) { + + var segment = segments[i]; + if (segment.isVisible) { + var segmentBBox = segment.bbox(); + bbox = bbox ? bbox.union(segmentBBox) : segmentBBox; + } + } + + if (bbox) return bbox; + + // if the path has only invisible elements, return end point of last segment + var lastSegment = segments[numSegments - 1]; + return new Rect(lastSegment.end.x, lastSegment.end.y, 0, 0); + }, + + // Returns a new path that is a clone of this path. + clone: function() { + + var segments = this.segments; + var numSegments = segments.length; + // works even if path has no segments + + var path = new Path(); + for (var i = 0; i < numSegments; i++) { + + var segment = segments[i].clone(); + path.appendSegment(segment); + } + + return path; + }, + + closestPoint: function(p, opt) { + + var t = this.closestPointT(p, opt); + if (!t) return null; + + return this.pointAtT(t); + }, + + closestPointLength: function(p, opt) { + + opt = opt || {}; + var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; + var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions; + var localOpt = { precision: precision, segmentSubdivisions: segmentSubdivisions }; + + var t = this.closestPointT(p, localOpt); + if (!t) return 0; + + return this.lengthAtT(t, localOpt); + }, + + closestPointNormalizedLength: function(p, opt) { + + opt = opt || {}; + var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; + var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions; + var localOpt = { precision: precision, segmentSubdivisions: segmentSubdivisions }; + + var cpLength = this.closestPointLength(p, localOpt); + if (cpLength === 0) return 0; // shortcut + + var length = this.length(localOpt); + if (length === 0) return 0; // prevents division by zero + + return cpLength / length; + }, + + // Private function. + closestPointT: function(p, opt) { + + var segments = this.segments; + var numSegments = segments.length; + if (numSegments === 0) return null; // if segments is an empty array + + opt = opt || {}; + var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; + var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions; + // not using localOpt + + var closestPointT; + var minSquaredDistance = Infinity; + for (var i = 0; i < numSegments; i++) { + + var segment = segments[i]; + var subdivisions = segmentSubdivisions[i]; + + if (segment.isVisible) { + var segmentClosestPointT = segment.closestPointT(p, { precision: precision, subdivisions: subdivisions }); + var segmentClosestPoint = segment.pointAtT(segmentClosestPointT); + var squaredDistance = (new Line(segmentClosestPoint, p)).squaredLength(); + + if (squaredDistance < minSquaredDistance) { + closestPointT = { segmentIndex: i, value: segmentClosestPointT }; + minSquaredDistance = squaredDistance; + } + } + } + + if (closestPointT) return closestPointT; + + // if no visible segment, return end of last segment + return { segmentIndex: numSegments - 1, value: 1 }; + }, + + closestPointTangent: function(p, opt) { + + var segments = this.segments; + var numSegments = segments.length; + if (numSegments === 0) return null; // if segments is an empty array + + opt = opt || {}; + var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; + var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions; + // not using localOpt + + var closestPointTangent; + var minSquaredDistance = Infinity; + for (var i = 0; i < numSegments; i++) { + + var segment = segments[i]; + var subdivisions = segmentSubdivisions[i]; + + if (segment.isDifferentiable()) { + var segmentClosestPointT = segment.closestPointT(p, { precision: precision, subdivisions: subdivisions }); + var segmentClosestPoint = segment.pointAtT(segmentClosestPointT); + var squaredDistance = (new Line(segmentClosestPoint, p)).squaredLength(); + + if (squaredDistance < minSquaredDistance) { + closestPointTangent = segment.tangentAtT(segmentClosestPointT); + minSquaredDistance = squaredDistance; + } + } + } + + if (closestPointTangent) return closestPointTangent; + + // if no valid segment, return null + return null; + }, + + // Checks whether two paths are exactly the same. + // If `p` is undefined or null, returns false. + equals: function(p) { + + if (!p) return false; + + var segments = this.segments; + var otherSegments = p.segments; + + var numSegments = segments.length; + if (otherSegments.length !== numSegments) return false; // if the two paths have different number of segments, they cannot be equal + + for (var i = 0; i < numSegments; i++) { + + var segment = segments[i]; + var otherSegment = otherSegments[i]; + + // as soon as an inequality is found in segments, return false + if ((segment.type !== otherSegment.type) || (!segment.equals(otherSegment))) return false; + } + + // if no inequality found in segments, return true + return true; + }, + + // Accepts negative indices. + // Throws an error if path has no segments. + // Throws an error if index is out of range. + getSegment: function(index) { + + var segments = this.segments; + var numSegments = segments.length; + if (!numSegments === 0) throw new Error('Path has no segments.'); + + if (index < 0) index = numSegments + index; // convert negative indices to positive + if (index >= numSegments || index < 0) throw new Error('Index out of range.'); + + return segments[index]; + }, + + // Returns an array of segment subdivisions, with precision better than requested `opt.precision`. + getSegmentSubdivisions: function(opt) { + + var segments = this.segments; + var numSegments = segments.length; + // works even if path has no segments + + opt = opt || {}; + var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; + // not using opt.segmentSubdivisions + // not using localOpt + + var segmentSubdivisions = []; + for (var i = 0; i < numSegments; i++) { + + var segment = segments[i]; + var subdivisions = segment.getSubdivisions({ precision: precision }); + segmentSubdivisions.push(subdivisions); + } + + return segmentSubdivisions; + }, + + // Insert `arg` at given `index`. + // `index = 0` means insert at the beginning. + // `index = segments.length` means insert at the end. + // Accepts negative indices, from `-1` to `-(segments.length + 1)`. + // Accepts one segment or an array of segments as argument. + // Throws an error if index is out of range. + // Throws an error if argument is not a segment or an array of segments. + insertSegment: function(index, arg) { + + var segments = this.segments; + var numSegments = segments.length; + // works even if path has no segments + + // note that these are incremented comapared to getSegments() + // we can insert after last element (note that this changes the meaning of index -1) + if (index < 0) index = numSegments + index + 1; // convert negative indices to positive + if (index > numSegments || index < 0) throw new Error('Index out of range.'); + + var currentSegment; + + var previousSegment = null; + var nextSegment = null; + + if (numSegments !== 0) { + if (index >= 1) { + previousSegment = segments[index - 1]; + nextSegment = previousSegment.nextSegment; // if we are inserting at end, nextSegment is null + + } else { // if index === 0 + // previousSegment is null + nextSegment = segments[0]; + } + } + + if (!Array.isArray(arg)) { + if (!arg || !arg.isSegment) throw new Error('Segment required.'); + + currentSegment = this.prepareSegment(arg, previousSegment, nextSegment); + segments.splice(index, 0, currentSegment); + + } else { + if (!arg[0].isSegment) throw new Error('Segments required.'); + + var n = arg.length; + for (var i = 0; i < n; i++) { + + var currentArg = arg[i]; + currentSegment = this.prepareSegment(currentArg, previousSegment, nextSegment); + segments.splice((index + i), 0, currentSegment); // incrementing index to insert subsequent segments after inserted segments + previousSegment = currentSegment; + } + } + }, + + isDifferentiable: function() { + + var segments = this.segments; + var numSegments = segments.length; + + for (var i = 0; i < numSegments; i++) { + + var segment = segments[i]; + // as soon as a differentiable segment is found in segments, return true + if (segment.isDifferentiable()) return true; + } + + // if no differentiable segment is found in segments, return false + return false; + }, + + // Checks whether current path segments are valid. + // Note that d is allowed to be empty - should disable rendering of the path. + isValid: function() { + + var segments = this.segments; + var isValid = (segments.length === 0) || (segments[0].type === 'M'); // either empty or first segment is a Moveto + return isValid; + }, + + // Returns length of the path, with precision better than requested `opt.precision`; or using `opt.segmentSubdivisions` provided. + // If path has no segments, returns 0. + length: function(opt) { + + var segments = this.segments; + var numSegments = segments.length; + if (numSegments === 0) return 0; // if segments is an empty array + + opt = opt || {}; + var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; // opt.precision only used in getSegmentSubdivisions() call + var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions; + // not using localOpt + + var length = 0; + for (var i = 0; i < numSegments; i++) { + + var segment = segments[i]; + var subdivisions = segmentSubdivisions[i]; + length += segment.length({ subdivisions: subdivisions }); + } + + return length; + }, + + // Private function. + lengthAtT: function(t, opt) { + + var segments = this.segments; + var numSegments = segments.length; + if (numSegments === 0) return 0; // if segments is an empty array + + var segmentIndex = t.segmentIndex; + if (segmentIndex < 0) return 0; // regardless of t.value + + var tValue = t.value; + if (segmentIndex >= numSegments) { + segmentIndex = numSegments - 1; + tValue = 1; + } + else if (tValue < 0) tValue = 0; + else if (tValue > 1) tValue = 1; + + opt = opt || {}; + var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; + var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions; + // not using localOpt + + var subdivisions; + var length = 0; + for (var i = 0; i < segmentIndex; i++) { + + var segment = segments[i]; + subdivisions = segmentSubdivisions[i]; + length += segment.length({ precisison: precision, subdivisions: subdivisions }); + } + + segment = segments[segmentIndex]; + subdivisions = segmentSubdivisions[segmentIndex]; + length += segment.lengthAtT(tValue, { precisison: precision, subdivisions: subdivisions }); + + return length; + }, + + // Returns point at requested `ratio` between 0 and 1, with precision better than requested `opt.precision`; optionally using `opt.segmentSubdivisions` provided. + pointAt: function(ratio, opt) { + + var segments = this.segments; + var numSegments = segments.length; + if (numSegments === 0) return null; // if segments is an empty array + + if (ratio <= 0) return this.start.clone(); + if (ratio >= 1) return this.end.clone(); + + opt = opt || {}; + var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; + var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions; + var localOpt = { precision: precision, segmentSubdivisions: segmentSubdivisions }; + + var pathLength = this.length(localOpt); + var length = pathLength * ratio; + + return this.pointAtLength(length, localOpt); + }, + + // Returns point at requested `length`, with precision better than requested `opt.precision`; optionally using `opt.segmentSubdivisions` provided. + // Accepts negative length. + pointAtLength: function(length, opt) { + + var segments = this.segments; + var numSegments = segments.length; + if (numSegments === 0) return null; // if segments is an empty array + + if (length === 0) return this.start.clone(); + + var fromStart = true; + if (length < 0) { + fromStart = false; // negative lengths mean start calculation from end point + length = -length; // absolute value + } + + opt = opt || {}; + var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; + var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions; + // not using localOpt + + var lastVisibleSegment; + var l = 0; // length so far + for (var i = (fromStart ? 0 : (numSegments - 1)); (fromStart ? (i < numSegments) : (i >= 0)); (fromStart ? (i++) : (i--))) { + + var segment = segments[i]; + var subdivisions = segmentSubdivisions[i]; + var d = segment.length({ precision: precision, subdivisions: subdivisions }); + + if (segment.isVisible) { + if (length <= (l + d)) { + return segment.pointAtLength(((fromStart ? 1 : -1) * (length - l)), { precision: precision, subdivisions: subdivisions }); + } + + lastVisibleSegment = segment; + } + + l += d; + } + + // if length requested is higher than the length of the path, return last visible segment endpoint + if (lastVisibleSegment) return (fromStart ? lastVisibleSegment.end : lastVisibleSegment.start); + + // if no visible segment, return last segment end point (no matter if fromStart or no) + var lastSegment = segments[numSegments - 1]; + return lastSegment.end.clone(); + }, + + // Private function. + pointAtT: function(t) { + + var segments = this.segments; + var numSegments = segments.length; + if (numSegments === 0) return null; // if segments is an empty array + + var segmentIndex = t.segmentIndex; + if (segmentIndex < 0) return segments[0].pointAtT(0); + if (segmentIndex >= numSegments) return segments[numSegments - 1].pointAtT(1); + + var tValue = t.value; + if (tValue < 0) tValue = 0; + else if (tValue > 1) tValue = 1; + + return segments[segmentIndex].pointAtT(tValue); + }, + + // Helper method for adding segments. + prepareSegment: function(segment, previousSegment, nextSegment) { + + // insert after previous segment and before previous segment's next segment + segment.previousSegment = previousSegment; + segment.nextSegment = nextSegment; + if (previousSegment) previousSegment.nextSegment = segment; + if (nextSegment) nextSegment.previousSegment = segment; + + var updateSubpathStart = segment; + if (segment.isSubpathStart) { + segment.subpathStartSegment = segment; // assign self as subpath start segment + updateSubpathStart = nextSegment; // start updating from next segment + } + + // assign previous segment's subpath start (or self if it is a subpath start) to subsequent segments + if (updateSubpathStart) this.updateSubpathStartSegment(updateSubpathStart); + + return segment; + }, + + // Default precision + PRECISION: 3, + + // Remove the segment at `index`. + // Accepts negative indices, from `-1` to `-segments.length`. + // Throws an error if path has no segments. + // Throws an error if index is out of range. + removeSegment: function(index) { + + var segments = this.segments; + var numSegments = segments.length; + if (numSegments === 0) throw new Error('Path has no segments.'); + + if (index < 0) index = numSegments + index; // convert negative indices to positive + if (index >= numSegments || index < 0) throw new Error('Index out of range.'); + + var removedSegment = segments.splice(index, 1)[0]; + var previousSegment = removedSegment.previousSegment; + var nextSegment = removedSegment.nextSegment; + + // link the previous and next segments together (if present) + if (previousSegment) previousSegment.nextSegment = nextSegment; // may be null + if (nextSegment) nextSegment.previousSegment = previousSegment; // may be null + + // if removed segment used to start a subpath, update all subsequent segments until another subpath start segment is reached + if (removedSegment.isSubpathStart && nextSegment) this.updateSubpathStartSegment(nextSegment); + }, + + // Replace the segment at `index` with `arg`. + // Accepts negative indices, from `-1` to `-segments.length`. + // Accepts one segment or an array of segments as argument. + // Throws an error if path has no segments. + // Throws an error if index is out of range. + // Throws an error if argument is not a segment or an array of segments. + replaceSegment: function(index, arg) { + + var segments = this.segments; + var numSegments = segments.length; + if (numSegments === 0) throw new Error('Path has no segments.'); + + if (index < 0) index = numSegments + index; // convert negative indices to positive + if (index >= numSegments || index < 0) throw new Error('Index out of range.'); + + var currentSegment; + + var replacedSegment = segments[index]; + var previousSegment = replacedSegment.previousSegment; + var nextSegment = replacedSegment.nextSegment; + + var updateSubpathStart = replacedSegment.isSubpathStart; // boolean: is an update of subpath starts necessary? + + if (!Array.isArray(arg)) { + if (!arg || !arg.isSegment) throw new Error('Segment required.'); + + currentSegment = this.prepareSegment(arg, previousSegment, nextSegment); + segments.splice(index, 1, currentSegment); // directly replace + + if (updateSubpathStart && currentSegment.isSubpathStart) updateSubpathStart = false; // already updated by `prepareSegment` + + } else { + if (!arg[0].isSegment) throw new Error('Segments required.'); + + segments.splice(index, 1); + + var n = arg.length; + for (var i = 0; i < n; i++) { + + var currentArg = arg[i]; + currentSegment = this.prepareSegment(currentArg, previousSegment, nextSegment); + segments.splice((index + i), 0, currentSegment); // incrementing index to insert subsequent segments after inserted segments + previousSegment = currentSegment; + + if (updateSubpathStart && currentSegment.isSubpathStart) updateSubpathStart = false; // already updated by `prepareSegment` + } + } + + // if replaced segment used to start a subpath and no new subpath start was added, update all subsequent segments until another subpath start segment is reached + if (updateSubpathStart && nextSegment) this.updateSubpathStartSegment(nextSegment); + }, + + scale: function(sx, sy, origin) { + + var segments = this.segments; + var numSegments = segments.length; + + for (var i = 0; i < numSegments; i++) { + + var segment = segments[i]; + segment.scale(sx, sy, origin); + } + + return this; + }, + + segmentAt: function(ratio, opt) { + + var index = this.segmentIndexAt(ratio, opt); + if (!index) return null; + + return this.getSegment(index); + }, + + // Accepts negative length. + segmentAtLength: function(length, opt) { + + var index = this.segmentIndexAtLength(length, opt); + if (!index) return null; + + return this.getSegment(index); + }, + + segmentIndexAt: function(ratio, opt) { + + var segments = this.segments; + var numSegments = segments.length; + if (numSegments === 0) return null; // if segments is an empty array + + if (ratio < 0) ratio = 0; + if (ratio > 1) ratio = 1; + + opt = opt || {}; + var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; + var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions; + var localOpt = { precision: precision, segmentSubdivisions: segmentSubdivisions }; + + var pathLength = this.length(localOpt); + var length = pathLength * ratio; + + return this.segmentIndexAtLength(length, localOpt); + }, + + toPoints: function(opt) { + + var segments = this.segments; + var numSegments = segments.length; + if (numSegments === 0) return null; // if segments is an empty array + + opt = opt || {}; + var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; + var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions; + + var points = []; + var partialPoints = []; + for (var i = 0; i < numSegments; i++) { + var segment = segments[i]; + if (segment.isVisible) { + var currentSegmentSubdivisions = segmentSubdivisions[i]; + if (currentSegmentSubdivisions.length > 0) { + var subdivisionPoints = currentSegmentSubdivisions.map(function(curve) { + return curve.start; + }); + Array.prototype.push.apply(partialPoints, subdivisionPoints); + } else { + partialPoints.push(segment.start); + } + } else if (partialPoints.length > 0) { + partialPoints.push(segments[i - 1].end); + points.push(partialPoints); + partialPoints = []; + } + } + + if (partialPoints.length > 0) { + partialPoints.push(this.end); + points.push(partialPoints); + } + return points; + }, + + toPolylines: function(opt) { + + var polylines = []; + var points = this.toPoints(opt); + if (!points) return null; + for (var i = 0, n = points.length; i < n; i++) { + polylines.push(new Polyline(points[i])); + } + + return polylines; + }, + + intersectionWithLine: function(line, opt) { + + var intersection = null; + var polylines = this.toPolylines(opt); + if (!polylines) return null; + for (var i = 0, n = polylines.length; i < n; i++) { + var polyline = polylines[i]; + var polylineIntersection = line.intersect(polyline); + if (polylineIntersection) { + intersection || (intersection = []); + if (Array.isArray(polylineIntersection)) { + Array.prototype.push.apply(intersection, polylineIntersection); + } else { + intersection.push(polylineIntersection); + } + } + } + + return intersection; + }, + + // Accepts negative length. + segmentIndexAtLength: function(length, opt) { + + var segments = this.segments; + var numSegments = segments.length; + if (numSegments === 0) return null; // if segments is an empty array + + var fromStart = true; + if (length < 0) { + fromStart = false; // negative lengths mean start calculation from end point + length = -length; // absolute value + } + + opt = opt || {}; + var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; + var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions; + // not using localOpt + + var lastVisibleSegmentIndex = null; + var l = 0; // length so far + for (var i = (fromStart ? 0 : (numSegments - 1)); (fromStart ? (i < numSegments) : (i >= 0)); (fromStart ? (i++) : (i--))) { + + var segment = segments[i]; + var subdivisions = segmentSubdivisions[i]; + var d = segment.length({ precision: precision, subdivisions: subdivisions }); + + if (segment.isVisible) { + if (length <= (l + d)) return i; + lastVisibleSegmentIndex = i; + } + + l += d; + } + + // if length requested is higher than the length of the path, return last visible segment index + // if no visible segment, return null + return lastVisibleSegmentIndex; + }, + + // Returns tangent line at requested `ratio` between 0 and 1, with precision better than requested `opt.precision`; optionally using `opt.segmentSubdivisions` provided. + tangentAt: function(ratio, opt) { + + var segments = this.segments; + var numSegments = segments.length; + if (numSegments === 0) return null; // if segments is an empty array + + if (ratio < 0) ratio = 0; + if (ratio > 1) ratio = 1; + + opt = opt || {}; + var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; + var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions; + var localOpt = { precision: precision, segmentSubdivisions: segmentSubdivisions }; + + var pathLength = this.length(localOpt); + var length = pathLength * ratio; + + return this.tangentAtLength(length, localOpt); + }, + + // Returns tangent line at requested `length`, with precision better than requested `opt.precision`; optionally using `opt.segmentSubdivisions` provided. + // Accepts negative length. + tangentAtLength: function(length, opt) { + + var segments = this.segments; + var numSegments = segments.length; + if (numSegments === 0) return null; // if segments is an empty array + + var fromStart = true; + if (length < 0) { + fromStart = false; // negative lengths mean start calculation from end point + length = -length; // absolute value + } + + opt = opt || {}; + var precision = (opt.precision === undefined) ? this.PRECISION : opt.precision; + var segmentSubdivisions = (opt.segmentSubdivisions === undefined) ? this.getSegmentSubdivisions({ precision: precision }) : opt.segmentSubdivisions; + // not using localOpt + + var lastValidSegment; // visible AND differentiable (with a tangent) + var l = 0; // length so far + for (var i = (fromStart ? 0 : (numSegments - 1)); (fromStart ? (i < numSegments) : (i >= 0)); (fromStart ? (i++) : (i--))) { + + var segment = segments[i]; + var subdivisions = segmentSubdivisions[i]; + var d = segment.length({ precision: precision, subdivisions: subdivisions }); + + if (segment.isDifferentiable()) { + if (length <= (l + d)) { + return segment.tangentAtLength(((fromStart ? 1 : -1) * (length - l)), { precision: precision, subdivisions: subdivisions }); + } + + lastValidSegment = segment; + } + + l += d; + } + + // if length requested is higher than the length of the path, return tangent of endpoint of last valid segment + if (lastValidSegment) { + var t = (fromStart ? 1 : 0); + return lastValidSegment.tangentAtT(t); + } + + // if no valid segment, return null + return null; + }, + + // Private function. + tangentAtT: function(t) { + + var segments = this.segments; + var numSegments = segments.length; + if (numSegments === 0) return null; // if segments is an empty array + + var segmentIndex = t.segmentIndex; + if (segmentIndex < 0) return segments[0].tangentAtT(0); + if (segmentIndex >= numSegments) return segments[numSegments - 1].tangentAtT(1); + + var tValue = t.value; + if (tValue < 0) tValue = 0; + else if (tValue > 1) tValue = 1; + + return segments[segmentIndex].tangentAtT(tValue); + }, + + translate: function(tx, ty) { + + var segments = this.segments; + var numSegments = segments.length; + + for (var i = 0; i < numSegments; i++) { + + var segment = segments[i]; + segment.translate(tx, ty); + } + + return this; + }, + + // Helper method for updating subpath start of segments, starting with the one provided. + updateSubpathStartSegment: function(segment) { + + var previousSegment = segment.previousSegment; // may be null + while (segment && !segment.isSubpathStart) { + + // assign previous segment's subpath start segment to this segment + if (previousSegment) segment.subpathStartSegment = previousSegment.subpathStartSegment; // may be null + else segment.subpathStartSegment = null; // if segment had no previous segment, assign null - creates an invalid path! + + previousSegment = segment; + segment = segment.nextSegment; // move on to the segment after etc. + } + }, + + // Returns a string that can be used to reconstruct the path. + // Additional error checking compared to toString (must start with M segment). + serialize: function() { + + if (!this.isValid()) throw new Error('Invalid path segments.'); + + return this.toString(); + }, + + toString: function() { + + var segments = this.segments; + var numSegments = segments.length; + + var pathData = ''; + for (var i = 0; i < numSegments; i++) { + + var segment = segments[i]; + pathData += segment.serialize() + ' '; + } + + return pathData.trim(); + } + }; + + Object.defineProperty(Path.prototype, 'start', { + // Getter for the first visible endpoint of the path. + + configurable: true, + + enumerable: true, + + get: function() { + + var segments = this.segments; + var numSegments = segments.length; + if (numSegments === 0) return null; + + for (var i = 0; i < numSegments; i++) { + + var segment = segments[i]; + if (segment.isVisible) return segment.start; + } + + // if no visible segment, return last segment end point + return segments[numSegments - 1].end; + } + }); + + Object.defineProperty(Path.prototype, 'end', { + // Getter for the last visible endpoint of the path. + + configurable: true, + + enumerable: true, + + get: function() { + + var segments = this.segments; + var numSegments = segments.length; + if (numSegments === 0) return null; + + for (var i = numSegments - 1; i >= 0; i--) { + + var segment = segments[i]; + if (segment.isVisible) return segment.end; + } + + // if no visible segment, return last segment end point + return segments[numSegments - 1].end; + } + }); + + /* + Point is the most basic object consisting of x/y coordinate. + + Possible instantiations are: + * `Point(10, 20)` + * `new Point(10, 20)` + * `Point('10 20')` + * `Point(Point(10, 20))` + */ + var Point = g.Point = function(x, y) { + + if (!(this instanceof Point)) { + return new Point(x, y); + } + + if (typeof x === 'string') { + var xy = x.split(x.indexOf('@') === -1 ? ' ' : '@'); + x = parseFloat(xy[0]); + y = parseFloat(xy[1]); + + } else if (Object(x) === x) { + y = x.y; + x = x.x; + } + + this.x = x === undefined ? 0 : x; + this.y = y === undefined ? 0 : y; + }; + + // Alternative constructor, from polar coordinates. + // @param {number} Distance. + // @param {number} Angle in radians. + // @param {point} [optional] Origin. + Point.fromPolar = function(distance, angle, origin) { + + origin = (origin && new Point(origin)) || new Point(0, 0); + var x = abs(distance * cos(angle)); + var y = abs(distance * sin(angle)); + var deg = normalizeAngle(toDeg(angle)); + + if (deg < 90) { + y = -y; + + } else if (deg < 180) { + x = -x; + y = -y; + + } else if (deg < 270) { + x = -x; + } + + return new Point(origin.x + x, origin.y + y); + }; + + // Create a point with random coordinates that fall into the range `[x1, x2]` and `[y1, y2]`. + Point.random = function(x1, x2, y1, y2) { + + return new Point(floor(random() * (x2 - x1 + 1) + x1), floor(random() * (y2 - y1 + 1) + y1)); + }; + + Point.prototype = { + + // If point lies outside rectangle `r`, return the nearest point on the boundary of rect `r`, + // otherwise return point itself. + // (see Squeak Smalltalk, Point>>adhereTo:) + adhereToRect: function(r) { + + if (r.containsPoint(this)) { + return this; + } + + this.x = min(max(this.x, r.x), r.x + r.width); + this.y = min(max(this.y, r.y), r.y + r.height); + return this; + }, + + // Return the bearing between me and the given point. + bearing: function(point) { + + return (new Line(this, point)).bearing(); + }, + + // Returns change in angle from my previous position (-dx, -dy) to my new position + // relative to ref point. + changeInAngle: function(dx, dy, ref) { + + // Revert the translation and measure the change in angle around x-axis. + return this.clone().offset(-dx, -dy).theta(ref) - this.theta(ref); + }, + + clone: function() { + + return new Point(this); + }, + + difference: function(dx, dy) { + + if ((Object(dx) === dx)) { + dy = dx.y; + dx = dx.x; + } + + return new Point(this.x - (dx || 0), this.y - (dy || 0)); + }, + + // Returns distance between me and point `p`. + distance: function(p) { + + return (new Line(this, p)).length(); + }, + + squaredDistance: function(p) { + + return (new Line(this, p)).squaredLength(); + }, + + equals: function(p) { + + return !!p && + this.x === p.x && + this.y === p.y; + }, + + magnitude: function() { + + return sqrt((this.x * this.x) + (this.y * this.y)) || 0.01; + }, + + // Returns a manhattan (taxi-cab) distance between me and point `p`. + manhattanDistance: function(p) { + + return abs(p.x - this.x) + abs(p.y - this.y); + }, + + // Move point on line starting from ref ending at me by + // distance distance. + move: function(ref, distance) { + + var theta = toRad((new Point(ref)).theta(this)); + var offset = this.offset(cos(theta) * distance, -sin(theta) * distance); + return offset; + }, + + // Scales x and y such that the distance between the point and the origin (0,0) is equal to the given length. + normalize: function(length) { + + var scale = (length || 1) / this.magnitude(); + return this.scale(scale, scale); + }, + + // Offset me by the specified amount. + offset: function(dx, dy) { + + if ((Object(dx) === dx)) { + dy = dx.y; + dx = dx.x; + } + + this.x += dx || 0; + this.y += dy || 0; + return this; + }, + + // Returns a point that is the reflection of me with + // the center of inversion in ref point. + reflection: function(ref) { + + return (new Point(ref)).move(this, this.distance(ref)); + }, + + // Rotate point by angle around origin. + // Angle is flipped because this is a left-handed coord system (y-axis points downward). + rotate: function(origin, angle) { + + origin = origin || new g.Point(0, 0); + + angle = toRad(normalizeAngle(-angle)); + var cosAngle = cos(angle); + var sinAngle = sin(angle); + + var x = (cosAngle * (this.x - origin.x)) - (sinAngle * (this.y - origin.y)) + origin.x; + var y = (sinAngle * (this.x - origin.x)) + (cosAngle * (this.y - origin.y)) + origin.y; + + this.x = x; + this.y = y; + return this; + }, + + round: function(precision) { + + var f = pow(10, precision || 0); + this.x = round(this.x * f) / f; + this.y = round(this.y * f) / f; + return this; + }, + + // Scale point with origin. + scale: function(sx, sy, origin) { + + origin = (origin && new Point(origin)) || new Point(0, 0); + this.x = origin.x + sx * (this.x - origin.x); + this.y = origin.y + sy * (this.y - origin.y); + return this; + }, + + snapToGrid: function(gx, gy) { + + this.x = snapToGrid(this.x, gx); + this.y = snapToGrid(this.y, gy || gx); + return this; + }, + + // Compute the angle between me and `p` and the x axis. + // (cartesian-to-polar coordinates conversion) + // Return theta angle in degrees. + theta: function(p) { + + p = new Point(p); + + // Invert the y-axis. + var y = -(p.y - this.y); + var x = p.x - this.x; + var rad = atan2(y, x); // defined for all 0 corner cases + + // Correction for III. and IV. quadrant. + if (rad < 0) { + rad = 2 * PI + rad; + } + + return 180 * rad / PI; + }, + + // Compute the angle between vector from me to p1 and the vector from me to p2. + // ordering of points p1 and p2 is important! + // theta function's angle convention: + // returns angles between 0 and 180 when the angle is counterclockwise + // returns angles between 180 and 360 to convert clockwise angles into counterclockwise ones + // returns NaN if any of the points p1, p2 is coincident with this point + angleBetween: function(p1, p2) { + + var angleBetween = (this.equals(p1) || this.equals(p2)) ? NaN : (this.theta(p2) - this.theta(p1)); + + if (angleBetween < 0) { + angleBetween += 360; // correction to keep angleBetween between 0 and 360 + } + + return angleBetween; + }, + + // Compute the angle between the vector from 0,0 to me and the vector from 0,0 to p. + // Returns NaN if p is at 0,0. + vectorAngle: function(p) { + + var zero = new Point(0,0); + return zero.angleBetween(this, p); + }, + + toJSON: function() { + + return { x: this.x, y: this.y }; + }, + + // Converts rectangular to polar coordinates. + // An origin can be specified, otherwise it's 0@0. + toPolar: function(o) { + + o = (o && new Point(o)) || new Point(0, 0); + var x = this.x; + var y = this.y; + this.x = sqrt((x - o.x) * (x - o.x) + (y - o.y) * (y - o.y)); // r + this.y = toRad(o.theta(new Point(x, y))); + return this; + }, + + toString: function() { + + return this.x + '@' + this.y; + }, + + update: function(x, y) { + + this.x = x || 0; + this.y = y || 0; + return this; + }, + + // Returns the dot product of this point with given other point + dot: function(p) { + + return p ? (this.x * p.x + this.y * p.y) : NaN; + }, + + // Returns the cross product of this point relative to two other points + // this point is the common point + // point p1 lies on the first vector, point p2 lies on the second vector + // watch out for the ordering of points p1 and p2! + // positive result indicates a clockwise ("right") turn from first to second vector + // negative result indicates a counterclockwise ("left") turn from first to second vector + // note that the above directions are reversed from the usual answer on the Internet + // that is because we are in a left-handed coord system (because the y-axis points downward) + cross: function(p1, p2) { + + return (p1 && p2) ? (((p2.x - this.x) * (p1.y - this.y)) - ((p2.y - this.y) * (p1.x - this.x))) : NaN; + }, + + + // Linear interpolation + lerp: function(p, t) { + + var x = this.x; + var y = this.y; + return new Point((1 - t) * x + t * p.x, (1 - t) * y + t * p.y); + } + }; + + Point.prototype.translate = Point.prototype.offset; + + var Rect = g.Rect = function(x, y, w, h) { + + if (!(this instanceof Rect)) { + return new Rect(x, y, w, h); + } + + if ((Object(x) === x)) { + y = x.y; + w = x.width; + h = x.height; + x = x.x; + } + + this.x = x === undefined ? 0 : x; + this.y = y === undefined ? 0 : y; + this.width = w === undefined ? 0 : w; + this.height = h === undefined ? 0 : h; + }; + + Rect.fromEllipse = function(e) { + + e = new Ellipse(e); + return new Rect(e.x - e.a, e.y - e.b, 2 * e.a, 2 * e.b); + }; + + Rect.prototype = { + + // Find my bounding box when I'm rotated with the center of rotation in the center of me. + // @return r {rectangle} representing a bounding box + bbox: function(angle) { + + if (!angle) return this.clone(); + + var theta = toRad(angle || 0); + var st = abs(sin(theta)); + var ct = abs(cos(theta)); + var w = this.width * ct + this.height * st; + var h = this.width * st + this.height * ct; + return new Rect(this.x + (this.width - w) / 2, this.y + (this.height - h) / 2, w, h); + }, + + bottomLeft: function() { + + return new Point(this.x, this.y + this.height); + }, + + bottomLine: function() { + + return new Line(this.bottomLeft(), this.bottomRight()); + }, + + bottomMiddle: function() { + + return new Point(this.x + this.width / 2, this.y + this.height); + }, + + center: function() { + + return new Point(this.x + this.width / 2, this.y + this.height / 2); + }, + + clone: function() { + + return new Rect(this); + }, + + // @return {bool} true if point p is insight me + containsPoint: function(p) { + + p = new Point(p); + return p.x >= this.x && p.x <= this.x + this.width && p.y >= this.y && p.y <= this.y + this.height; + }, + + // @return {bool} true if rectangle `r` is inside me. + containsRect: function(r) { + + var r0 = new Rect(this).normalize(); + var r1 = new Rect(r).normalize(); + var w0 = r0.width; + var h0 = r0.height; + var w1 = r1.width; + var h1 = r1.height; + + if (!w0 || !h0 || !w1 || !h1) { + // At least one of the dimensions is 0 + return false; + } + + var x0 = r0.x; + var y0 = r0.y; + var x1 = r1.x; + var y1 = r1.y; + + w1 += x1; + w0 += x0; + h1 += y1; + h0 += y0; + + return x0 <= x1 && w1 <= w0 && y0 <= y1 && h1 <= h0; + }, + + corner: function() { + + return new Point(this.x + this.width, this.y + this.height); + }, + + // @return {boolean} true if rectangles are equal. + equals: function(r) { + + var mr = (new Rect(this)).normalize(); + var nr = (new Rect(r)).normalize(); + return mr.x === nr.x && mr.y === nr.y && mr.width === nr.width && mr.height === nr.height; + }, + + // @return {rect} if rectangles intersect, {null} if not. + intersect: function(r) { + + var myOrigin = this.origin(); + var myCorner = this.corner(); + var rOrigin = r.origin(); + var rCorner = r.corner(); + + // No intersection found + if (rCorner.x <= myOrigin.x || + rCorner.y <= myOrigin.y || + rOrigin.x >= myCorner.x || + rOrigin.y >= myCorner.y) return null; + + var x = max(myOrigin.x, rOrigin.x); + var y = max(myOrigin.y, rOrigin.y); + + return new Rect(x, y, min(myCorner.x, rCorner.x) - x, min(myCorner.y, rCorner.y) - y); + }, + + intersectionWithLine: function(line) { + + var r = this; + var rectLines = [ r.topLine(), r.rightLine(), r.bottomLine(), r.leftLine() ]; + var points = []; + var dedupeArr = []; + var pt, i; + + var n = rectLines.length; + for (i = 0; i < n; i ++) { + + pt = line.intersect(rectLines[i]); + if (pt !== null && dedupeArr.indexOf(pt.toString()) < 0) { + points.push(pt); + dedupeArr.push(pt.toString()); + } + } + + return points.length > 0 ? points : null; + }, + + // Find point on my boundary where line starting + // from my center ending in point p intersects me. + // @param {number} angle If angle is specified, intersection with rotated rectangle is computed. + intersectionWithLineFromCenterToPoint: function(p, angle) { + + p = new Point(p); + var center = new Point(this.x + this.width / 2, this.y + this.height / 2); + var result; + + if (angle) p.rotate(center, angle); + + // (clockwise, starting from the top side) + var sides = [ + this.topLine(), + this.rightLine(), + this.bottomLine(), + this.leftLine() + ]; + var connector = new Line(center, p); + + for (var i = sides.length - 1; i >= 0; --i) { + var intersection = sides[i].intersection(connector); + if (intersection !== null) { + result = intersection; + break; + } + } + if (result && angle) result.rotate(center, -angle); + return result; + }, + + leftLine: function() { + + return new Line(this.topLeft(), this.bottomLeft()); + }, + + leftMiddle: function() { + + return new Point(this.x , this.y + this.height / 2); + }, + + // Move and expand me. + // @param r {rectangle} representing deltas + moveAndExpand: function(r) { + + this.x += r.x || 0; + this.y += r.y || 0; + this.width += r.width || 0; + this.height += r.height || 0; + return this; + }, + + // Offset me by the specified amount. + offset: function(dx, dy) { + + // pretend that this is a point and call offset() + // rewrites x and y according to dx and dy + return Point.prototype.offset.call(this, dx, dy); + }, + + // inflate by dx and dy, recompute origin [x, y] + // @param dx {delta_x} representing additional size to x + // @param dy {delta_y} representing additional size to y - + // dy param is not required -> in that case y is sized by dx + inflate: function(dx, dy) { + + if (dx === undefined) { + dx = 0; + } + + if (dy === undefined) { + dy = dx; + } + + this.x -= dx; + this.y -= dy; + this.width += 2 * dx; + this.height += 2 * dy; + + return this; + }, + + // Normalize the rectangle; i.e., make it so that it has a non-negative width and height. + // If width < 0 the function swaps the left and right corners, + // and it swaps the top and bottom corners if height < 0 + // like in http://qt-project.org/doc/qt-4.8/qrectf.html#normalized + normalize: function() { + + var newx = this.x; + var newy = this.y; + var newwidth = this.width; + var newheight = this.height; + if (this.width < 0) { + newx = this.x + this.width; + newwidth = -this.width; + } + if (this.height < 0) { + newy = this.y + this.height; + newheight = -this.height; + } + this.x = newx; + this.y = newy; + this.width = newwidth; + this.height = newheight; + return this; + }, + + origin: function() { + + return new Point(this.x, this.y); + }, + + // @return {point} a point on my boundary nearest to the given point. + // @see Squeak Smalltalk, Rectangle>>pointNearestTo: + pointNearestToPoint: function(point) { + + point = new Point(point); + if (this.containsPoint(point)) { + var side = this.sideNearestToPoint(point); + switch (side){ + case 'right': return new Point(this.x + this.width, point.y); + case 'left': return new Point(this.x, point.y); + case 'bottom': return new Point(point.x, this.y + this.height); + case 'top': return new Point(point.x, this.y); + } + } + return point.adhereToRect(this); + }, + + rightLine: function() { + + return new Line(this.topRight(), this.bottomRight()); + }, + + rightMiddle: function() { + + return new Point(this.x + this.width, this.y + this.height / 2); + }, + + round: function(precision) { + + var f = pow(10, precision || 0); + this.x = round(this.x * f) / f; + this.y = round(this.y * f) / f; + this.width = round(this.width * f) / f; + this.height = round(this.height * f) / f; + return this; + }, + + // Scale rectangle with origin. + scale: function(sx, sy, origin) { + + origin = this.origin().scale(sx, sy, origin); + this.x = origin.x; + this.y = origin.y; + this.width *= sx; + this.height *= sy; + return this; + }, + + maxRectScaleToFit: function(rect, origin) { + + rect = new Rect(rect); + origin || (origin = rect.center()); + + var sx1, sx2, sx3, sx4, sy1, sy2, sy3, sy4; + var ox = origin.x; + var oy = origin.y; + + // Here we find the maximal possible scale for all corner points (for x and y axis) of the rectangle, + // so when the scale is applied the point is still inside the rectangle. + + sx1 = sx2 = sx3 = sx4 = sy1 = sy2 = sy3 = sy4 = Infinity; + + // Top Left + var p1 = rect.topLeft(); + if (p1.x < ox) { + sx1 = (this.x - ox) / (p1.x - ox); + } + if (p1.y < oy) { + sy1 = (this.y - oy) / (p1.y - oy); + } + // Bottom Right + var p2 = rect.bottomRight(); + if (p2.x > ox) { + sx2 = (this.x + this.width - ox) / (p2.x - ox); + } + if (p2.y > oy) { + sy2 = (this.y + this.height - oy) / (p2.y - oy); + } + // Top Right + var p3 = rect.topRight(); + if (p3.x > ox) { + sx3 = (this.x + this.width - ox) / (p3.x - ox); + } + if (p3.y < oy) { + sy3 = (this.y - oy) / (p3.y - oy); + } + // Bottom Left + var p4 = rect.bottomLeft(); + if (p4.x < ox) { + sx4 = (this.x - ox) / (p4.x - ox); + } + if (p4.y > oy) { + sy4 = (this.y + this.height - oy) / (p4.y - oy); + } + + return { + sx: min(sx1, sx2, sx3, sx4), + sy: min(sy1, sy2, sy3, sy4) + }; + }, + + maxRectUniformScaleToFit: function(rect, origin) { + + var scale = this.maxRectScaleToFit(rect, origin); + return min(scale.sx, scale.sy); + }, + + // @return {string} (left|right|top|bottom) side which is nearest to point + // @see Squeak Smalltalk, Rectangle>>sideNearestTo: + sideNearestToPoint: function(point) { + + point = new Point(point); + var distToLeft = point.x - this.x; + var distToRight = (this.x + this.width) - point.x; + var distToTop = point.y - this.y; + var distToBottom = (this.y + this.height) - point.y; + var closest = distToLeft; + var side = 'left'; + + if (distToRight < closest) { + closest = distToRight; + side = 'right'; + } + if (distToTop < closest) { + closest = distToTop; + side = 'top'; + } + if (distToBottom < closest) { + closest = distToBottom; + side = 'bottom'; + } + return side; + }, + + snapToGrid: function(gx, gy) { + + var origin = this.origin().snapToGrid(gx, gy); + var corner = this.corner().snapToGrid(gx, gy); + this.x = origin.x; + this.y = origin.y; + this.width = corner.x - origin.x; + this.height = corner.y - origin.y; + return this; + }, + + topLine: function() { + + return new Line(this.topLeft(), this.topRight()); + }, + + topMiddle: function() { + + return new Point(this.x + this.width / 2, this.y); + }, + + topRight: function() { + + return new Point(this.x + this.width, this.y); + }, + + toJSON: function() { + + return { x: this.x, y: this.y, width: this.width, height: this.height }; + }, + + toString: function() { + + return this.origin().toString() + ' ' + this.corner().toString(); + }, + + // @return {rect} representing the union of both rectangles. + union: function(rect) { + + rect = new Rect(rect); + var myOrigin = this.origin(); + var myCorner = this.corner(); + var rOrigin = rect.origin(); + var rCorner = rect.corner(); + + var originX = min(myOrigin.x, rOrigin.x); + var originY = min(myOrigin.y, rOrigin.y); + var cornerX = max(myCorner.x, rCorner.x); + var cornerY = max(myCorner.y, rCorner.y); + + return new Rect(originX, originY, cornerX - originX, cornerY - originY); + } + }; + + Rect.prototype.bottomRight = Rect.prototype.corner; + + Rect.prototype.topLeft = Rect.prototype.origin; + + Rect.prototype.translate = Rect.prototype.offset; + + var Polyline = g.Polyline = function(points) { + + if (!(this instanceof Polyline)) { + return new Polyline(points); + } + + if (typeof points === 'string') { + return new Polyline.parse(points); + } + + this.points = (Array.isArray(points) ? points.map(Point) : []); + }; + + Polyline.parse = function(svgString) { + + if (svgString === '') return new Polyline(); + + var points = []; + + var coords = svgString.split(/\s|,/); + var n = coords.length; + for (var i = 0; i < n; i += 2) { + points.push({ x: +coords[i], y: +coords[i + 1] }); + } + + return new Polyline(points); + }; + + Polyline.prototype = { + + bbox: function() { + + var x1 = Infinity; + var x2 = -Infinity; + var y1 = Infinity; + var y2 = -Infinity; + + var points = this.points; + var numPoints = points.length; + if (numPoints === 0) return null; // if points array is empty + + for (var i = 0; i < numPoints; i++) { + + var point = points[i]; + var x = point.x; + var y = point.y; + + if (x < x1) x1 = x; + if (x > x2) x2 = x; + if (y < y1) y1 = y; + if (y > y2) y2 = y; + } + + return new Rect(x1, y1, x2 - x1, y2 - y1); + }, + + clone: function() { + + var points = this.points; + var numPoints = points.length; + if (numPoints === 0) return new Polyline(); // if points array is empty + + var newPoints = []; + for (var i = 0; i < numPoints; i++) { + + var point = points[i].clone(); + newPoints.push(point); + } + + return new Polyline(newPoints); + }, + + closestPoint: function(p) { + + var cpLength = this.closestPointLength(p); + + return this.pointAtLength(cpLength); + }, + + closestPointLength: function(p) { + + var points = this.points; + var numPoints = points.length; + if (numPoints === 0) return 0; // if points array is empty + if (numPoints === 1) return 0; // if there is only one point + + var cpLength; + var minSqrDistance = Infinity; + var length = 0; + var n = numPoints - 1; + for (var i = 0; i < n; i++) { + + var line = new Line(points[i], points[i + 1]); + var lineLength = line.length(); + + var cpNormalizedLength = line.closestPointNormalizedLength(p); + var cp = line.pointAt(cpNormalizedLength); + + var sqrDistance = cp.squaredDistance(p); + if (sqrDistance < minSqrDistance) { + minSqrDistance = sqrDistance; + cpLength = length + (cpNormalizedLength * lineLength); + } + + length += lineLength; + } + + return cpLength; + }, + + closestPointNormalizedLength: function(p) { + + var cpLength = this.closestPointLength(p); + if (cpLength === 0) return 0; // shortcut + + var length = this.length(); + if (length === 0) return 0; // prevents division by zero + + return cpLength / length; + }, + + closestPointTangent: function(p) { + + var cpLength = this.closestPointLength(p); + + return this.tangentAtLength(cpLength); + }, + + // Returns a convex-hull polyline from this polyline. + // Implements the Graham scan (https://en.wikipedia.org/wiki/Graham_scan). + // Output polyline starts at the first element of the original polyline that is on the hull, then continues clockwise. + // Minimal polyline is found (only vertices of the hull are reported, no collinear points). + convexHull: function() { + + var i; + var n; + + var points = this.points; + var numPoints = points.length; + if (numPoints === 0) return new Polyline(); // if points array is empty + + // step 1: find the starting point - point with the lowest y (if equality, highest x) + var startPoint; + for (i = 0; i < numPoints; i++) { + if (startPoint === undefined) { + // if this is the first point we see, set it as start point + startPoint = points[i]; + + } else if (points[i].y < startPoint.y) { + // start point should have lowest y from all points + startPoint = points[i]; + + } else if ((points[i].y === startPoint.y) && (points[i].x > startPoint.x)) { + // if two points have the lowest y, choose the one that has highest x + // there are no points to the right of startPoint - no ambiguity about theta 0 + // if there are several coincident start point candidates, first one is reported + startPoint = points[i]; + } + } + + // step 2: sort the list of points + // sorting by angle between line from startPoint to point and the x-axis (theta) + + // step 2a: create the point records = [point, originalIndex, angle] + var sortedPointRecords = []; + for (i = 0; i < numPoints; i++) { + + var angle = startPoint.theta(points[i]); + if (angle === 0) { + angle = 360; // give highest angle to start point + // the start point will end up at end of sorted list + // the start point will end up at beginning of hull points list + } + + var entry = [points[i], i, angle]; + sortedPointRecords.push(entry); + } + + // step 2b: sort the list in place + sortedPointRecords.sort(function(record1, record2) { + // returning a negative number here sorts record1 before record2 + // if first angle is smaller than second, first angle should come before second + + var sortOutput = record1[2] - record2[2]; // negative if first angle smaller + if (sortOutput === 0) { + // if the two angles are equal, sort by originalIndex + sortOutput = record2[1] - record1[1]; // negative if first index larger + // coincident points will be sorted in reverse-numerical order + // so the coincident points with lower original index will be considered first + } + + return sortOutput; + }); + + // step 2c: duplicate start record from the top of the stack to the bottom of the stack + if (sortedPointRecords.length > 2) { + var startPointRecord = sortedPointRecords[sortedPointRecords.length-1]; + sortedPointRecords.unshift(startPointRecord); + } + + // step 3a: go through sorted points in order and find those with right turns + // we want to get our results in clockwise order + var insidePoints = {}; // dictionary of points with left turns - cannot be on the hull + var hullPointRecords = []; // stack of records with right turns - hull point candidates + + var currentPointRecord; + var currentPoint; + var lastHullPointRecord; + var lastHullPoint; + var secondLastHullPointRecord; + var secondLastHullPoint; + while (sortedPointRecords.length !== 0) { + + currentPointRecord = sortedPointRecords.pop(); + currentPoint = currentPointRecord[0]; + + // check if point has already been discarded + // keys for insidePoints are stored in the form 'point.x@point.y@@originalIndex' + if (insidePoints.hasOwnProperty(currentPointRecord[0] + '@@' + currentPointRecord[1])) { + // this point had an incorrect turn at some previous iteration of this loop + // this disqualifies it from possibly being on the hull + continue; + } + + var correctTurnFound = false; + while (!correctTurnFound) { + + if (hullPointRecords.length < 2) { + // not enough points for comparison, just add current point + hullPointRecords.push(currentPointRecord); + correctTurnFound = true; + + } else { + lastHullPointRecord = hullPointRecords.pop(); + lastHullPoint = lastHullPointRecord[0]; + secondLastHullPointRecord = hullPointRecords.pop(); + secondLastHullPoint = secondLastHullPointRecord[0]; + + var crossProduct = secondLastHullPoint.cross(lastHullPoint, currentPoint); + + if (crossProduct < 0) { + // found a right turn + hullPointRecords.push(secondLastHullPointRecord); + hullPointRecords.push(lastHullPointRecord); + hullPointRecords.push(currentPointRecord); + correctTurnFound = true; + + } else if (crossProduct === 0) { + // the three points are collinear + // three options: + // there may be a 180 or 0 degree angle at lastHullPoint + // or two of the three points are coincident + var THRESHOLD = 1e-10; // we have to take rounding errors into account + var angleBetween = lastHullPoint.angleBetween(secondLastHullPoint, currentPoint); + if (abs(angleBetween - 180) < THRESHOLD) { // rouding around 180 to 180 + // if the cross product is 0 because the angle is 180 degrees + // discard last hull point (add to insidePoints) + //insidePoints.unshift(lastHullPoint); + insidePoints[lastHullPointRecord[0] + '@@' + lastHullPointRecord[1]] = lastHullPoint; + // reenter second-to-last hull point (will be last at next iter) + hullPointRecords.push(secondLastHullPointRecord); + // do not do anything with current point + // correct turn not found + + } else if (lastHullPoint.equals(currentPoint) || secondLastHullPoint.equals(lastHullPoint)) { + // if the cross product is 0 because two points are the same + // discard last hull point (add to insidePoints) + //insidePoints.unshift(lastHullPoint); + insidePoints[lastHullPointRecord[0] + '@@' + lastHullPointRecord[1]] = lastHullPoint; + // reenter second-to-last hull point (will be last at next iter) + hullPointRecords.push(secondLastHullPointRecord); + // do not do anything with current point + // correct turn not found + + } else if (abs(((angleBetween + 1) % 360) - 1) < THRESHOLD) { // rounding around 0 and 360 to 0 + // if the cross product is 0 because the angle is 0 degrees + // remove last hull point from hull BUT do not discard it + // reenter second-to-last hull point (will be last at next iter) + hullPointRecords.push(secondLastHullPointRecord); + // put last hull point back into the sorted point records list + sortedPointRecords.push(lastHullPointRecord); + // we are switching the order of the 0deg and 180deg points + // correct turn not found + } + + } else { + // found a left turn + // discard last hull point (add to insidePoints) + //insidePoints.unshift(lastHullPoint); + insidePoints[lastHullPointRecord[0] + '@@' + lastHullPointRecord[1]] = lastHullPoint; + // reenter second-to-last hull point (will be last at next iter of loop) + hullPointRecords.push(secondLastHullPointRecord); + // do not do anything with current point + // correct turn not found + } + } + } + } + // at this point, hullPointRecords contains the output points in clockwise order + // the points start with lowest-y,highest-x startPoint, and end at the same point + + // step 3b: remove duplicated startPointRecord from the end of the array + if (hullPointRecords.length > 2) { + hullPointRecords.pop(); + } + + // step 4: find the lowest originalIndex record and put it at the beginning of hull + var lowestHullIndex; // the lowest originalIndex on the hull + var indexOfLowestHullIndexRecord = -1; // the index of the record with lowestHullIndex + n = hullPointRecords.length; + for (i = 0; i < n; i++) { + + var currentHullIndex = hullPointRecords[i][1]; + + if (lowestHullIndex === undefined || currentHullIndex < lowestHullIndex) { + lowestHullIndex = currentHullIndex; + indexOfLowestHullIndexRecord = i; + } + } + + var hullPointRecordsReordered = []; + if (indexOfLowestHullIndexRecord > 0) { + var newFirstChunk = hullPointRecords.slice(indexOfLowestHullIndexRecord); + var newSecondChunk = hullPointRecords.slice(0, indexOfLowestHullIndexRecord); + hullPointRecordsReordered = newFirstChunk.concat(newSecondChunk); + + } else { + hullPointRecordsReordered = hullPointRecords; + } + + var hullPoints = []; + n = hullPointRecordsReordered.length; + for (i = 0; i < n; i++) { + hullPoints.push(hullPointRecordsReordered[i][0]); + } + + return new Polyline(hullPoints); + }, + + // Checks whether two polylines are exactly the same. + // If `p` is undefined or null, returns false. + equals: function(p) { + + if (!p) return false; + + var points = this.points; + var otherPoints = p.points; + + var numPoints = points.length; + if (otherPoints.length !== numPoints) return false; // if the two polylines have different number of points, they cannot be equal + + for (var i = 0; i < numPoints; i++) { + + var point = points[i]; + var otherPoint = p.points[i]; + + // as soon as an inequality is found in points, return false + if (!point.equals(otherPoint)) return false; + } + + // if no inequality found in points, return true + return true; + }, + + isDifferentiable: function() { + + var points = this.points; + var numPoints = points.length; + if (numPoints === 0) return false; + + var n = numPoints - 1; + for (var i = 0; i < n; i++) { + + var a = points[i]; + var b = points[i + 1]; + var line = new Line(a, b); + + // as soon as a differentiable line is found between two points, return true + if (line.isDifferentiable()) return true; + } + + // if no differentiable line is found between pairs of points, return false + return false; + }, + + length: function() { + + var points = this.points; + var numPoints = points.length; + if (numPoints === 0) return 0; // if points array is empty + + var length = 0; + var n = numPoints - 1; + for (var i = 0; i < n; i++) { + length += points[i].distance(points[i + 1]); + } + + return length; + }, + + pointAt: function(ratio) { + + var points = this.points; + var numPoints = points.length; + if (numPoints === 0) return null; // if points array is empty + if (numPoints === 1) return points[0].clone(); // if there is only one point + + if (ratio <= 0) return points[0].clone(); + if (ratio >= 1) return points[numPoints - 1].clone(); + + var polylineLength = this.length(); + var length = polylineLength * ratio; + + return this.pointAtLength(length); + }, + + pointAtLength: function(length) { + + var points = this.points; + var numPoints = points.length; + if (numPoints === 0) return null; // if points array is empty + if (numPoints === 1) return points[0].clone(); // if there is only one point + + var fromStart = true; + if (length < 0) { + fromStart = false; // negative lengths mean start calculation from end point + length = -length; // absolute value + } + + var l = 0; + var n = numPoints - 1; + for (var i = (fromStart ? 0 : (n - 1)); (fromStart ? (i < n) : (i >= 0)); (fromStart ? (i++) : (i--))) { + + var a = points[i]; + var b = points[i + 1]; + var line = new Line(a, b); + var d = a.distance(b); + + if (length <= (l + d)) { + return line.pointAtLength((fromStart ? 1 : -1) * (length - l)); + } + + l += d; + } + + // if length requested is higher than the length of the polyline, return last endpoint + var lastPoint = (fromStart ? points[numPoints - 1] : points[0]); + return lastPoint.clone(); + }, + + scale: function(sx, sy, origin) { + + var points = this.points; + var numPoints = points.length; + if (numPoints === 0) return this; // if points array is empty + + for (var i = 0; i < numPoints; i++) { + points[i].scale(sx, sy, origin); + } + + return this; + }, + + tangentAt: function(ratio) { + + var points = this.points; + var numPoints = points.length; + if (numPoints === 0) return null; // if points array is empty + if (numPoints === 1) return null; // if there is only one point + + if (ratio < 0) ratio = 0; + if (ratio > 1) ratio = 1; + + var polylineLength = this.length(); + var length = polylineLength * ratio; + + return this.tangentAtLength(length); + }, + + tangentAtLength: function(length) { + + var points = this.points; + var numPoints = points.length; + if (numPoints === 0) return null; // if points array is empty + if (numPoints === 1) return null; // if there is only one point + + var fromStart = true; + if (length < 0) { + fromStart = false; // negative lengths mean start calculation from end point + length = -length; // absolute value + } + + var lastValidLine; // differentiable (with a tangent) + var l = 0; // length so far + var n = numPoints - 1; + for (var i = (fromStart ? (0) : (n - 1)); (fromStart ? (i < n) : (i >= 0)); (fromStart ? (i++) : (i--))) { + + var a = points[i]; + var b = points[i + 1]; + var line = new Line(a, b); + var d = a.distance(b); + + if (line.isDifferentiable()) { // has a tangent line (line length is not 0) + if (length <= (l + d)) { + return line.tangentAtLength((fromStart ? 1 : -1) * (length - l)); + } + + lastValidLine = line; + } + + l += d; + } + + // if length requested is higher than the length of the polyline, return last valid endpoint + if (lastValidLine) { + var ratio = (fromStart ? 1 : 0); + return lastValidLine.tangentAt(ratio); + } + + // if no valid line, return null + return null; + }, + + intersectionWithLine: function(l) { + var line = new Line(l); + var intersections = []; + var points = this.points; + for (var i = 0, n = points.length - 1; i < n; i++) { + var a = points[i]; + var b = points[i+1]; + var l2 = new Line(a, b); + var int = line.intersectionWithLine(l2); + if (int) intersections.push(int[0]); + } + return (intersections.length > 0) ? intersections : null; + }, + + translate: function(tx, ty) { + + var points = this.points; + var numPoints = points.length; + if (numPoints === 0) return this; // if points array is empty + + for (var i = 0; i < numPoints; i++) { + points[i].translate(tx, ty); + } + + return this; + }, + + // Return svgString that can be used to recreate this line. + serialize: function() { + + var points = this.points; + var numPoints = points.length; + if (numPoints === 0) return ''; // if points array is empty + + var output = ''; + for (var i = 0; i < numPoints; i++) { + + var point = points[i]; + output += point.x + ',' + point.y + ' '; + } + + return output.trim(); + }, + + toString: function() { + + return this.points + ''; + } + }; + + Object.defineProperty(Polyline.prototype, 'start', { + // Getter for the first point of the polyline. + + configurable: true, + + enumerable: true, + + get: function() { + + var points = this.points; + var numPoints = points.length; + if (numPoints === 0) return null; // if points array is empty + + return this.points[0]; + }, + }); + + Object.defineProperty(Polyline.prototype, 'end', { + // Getter for the last point of the polyline. + + configurable: true, + + enumerable: true, + + get: function() { + + var points = this.points; + var numPoints = points.length; + if (numPoints === 0) return null; // if points array is empty + + return this.points[numPoints - 1]; + }, + }); + + g.scale = { + + // Return the `value` from the `domain` interval scaled to the `range` interval. + linear: function(domain, range, value) { + + var domainSpan = domain[1] - domain[0]; + var rangeSpan = range[1] - range[0]; + return (((value - domain[0]) / domainSpan) * rangeSpan + range[0]) || 0; + } + }; + + var normalizeAngle = g.normalizeAngle = function(angle) { + + return (angle % 360) + (angle < 0 ? 360 : 0); + }; + + var snapToGrid = g.snapToGrid = function(value, gridSize) { + + return gridSize * round(value / gridSize); + }; + + var toDeg = g.toDeg = function(rad) { + + return (180 * rad / PI) % 360; + }; + + var toRad = g.toRad = function(deg, over360) { + + over360 = over360 || false; + deg = over360 ? deg : (deg % 360); + return deg * PI / 180; + }; + + // For backwards compatibility: + g.ellipse = g.Ellipse; + g.line = g.Line; + g.point = g.Point; + g.rect = g.Rect; + + // Local helper function. + // Use an array of arguments to call a constructor (function called with `new`). + // Adapted from https://stackoverflow.com/a/8843181/2263595 + // It is not necessary to use this function if the arguments can be passed separately (i.e. if the number of arguments is limited). + // - If that is the case, use `new constructor(arg1, arg2)`, for example. + // It is not necessary to use this function if the function that needs an array of arguments is not supposed to be used as a constructor. + // - If that is the case, use `f.apply(thisArg, [arg1, arg2...])`, for example. + function applyToNew(constructor, argsArray) { + // The `new` keyword can only be applied to functions that take a limited number of arguments. + // - We can fake that with .bind(). + // - It calls a function (`constructor`, here) with the arguments that were provided to it - effectively transforming an unlimited number of arguments into limited. + // - So `new (constructor.bind(thisArg, arg1, arg2...))` + // - `thisArg` can be anything (e.g. null) because `new` keyword resets context to the constructor object. + // We need to pass in a variable number of arguments to the bind() call. + // - We can use .apply(). + // - So `new (constructor.bind.apply(constructor, [thisArg, arg1, arg2...]))` + // - `thisArg` can still be anything because `new` overwrites it. + // Finally, to make sure that constructor.bind overwriting is not a problem, we switch to `Function.prototype.bind`. + // - So, the final version is `new (Function.prototype.bind.apply(constructor, [thisArg, arg1, arg2...]))` + + // The function expects `argsArray[0]` to be `thisArg`. + // - This means that whatever is sent as the first element will be ignored. + // - The constructor will only see arguments starting from argsArray[1]. + // - So, a new dummy element is inserted at the start of the array. + argsArray.unshift(null); + + return new (Function.prototype.bind.apply(constructor, argsArray)); + } + + // Local helper function. + // Add properties from arguments on top of properties from `obj`. + // This allows for rudimentary inheritance. + // - The `obj` argument acts as parent. + // - This function creates a new object that inherits all `obj` properties and adds/replaces those that are present in arguments. + // - A high-level example: calling `extend(Vehicle, Car)` would be akin to declaring `class Car extends Vehicle`. + function extend(obj) { + // In JavaScript, the combination of a constructor function (e.g. `g.Line = function(...) {...}`) and prototype (e.g. `g.Line.prototype = {...}) is akin to a C++ class. + // - When inheritance is not necessary, we can leave it at that. (This would be akin to calling extend with only `obj`.) + // - But, what if we wanted the `g.Line` quasiclass to inherit from another quasiclass (let's call it `g.GeometryObject`) in JavaScript? + // - First, realize that both of those quasiclasses would still have their own separate constructor function. + // - So what we are actually saying is that we want the `g.Line` prototype to inherit from `g.GeometryObject` prototype. + // - This method provides a way to do exactly that. + // - It copies parent prototype's properties, then adds extra ones from child prototype/overrides parent prototype properties with child prototype properties. + // - Therefore, to continue with the example above: + // - `g.Line.prototype = extend(g.GeometryObject.prototype, linePrototype)` + // - Where `linePrototype` is a properties object that looks just like `g.Line.prototype` does right now. + // - Then, `g.Line` would allow the programmer to access to all methods currently in `g.Line.Prototype`, plus any non-overriden methods from `g.GeometryObject.prototype`. + // - In that aspect, `g.GeometryObject` would then act like the parent of `g.Line`. + // - Multiple inheritance is also possible, if multiple arguments are provided. + // - What if we wanted to add another level of abstraction between `g.GeometryObject` and `g.Line` (let's call it `g.LinearObject`)? + // - `g.Line.prototype = extend(g.GeometryObject.prototype, g.LinearObject.prototype, linePrototype)` + // - The ancestors are applied in order of appearance. + // - That means that `g.Line` would have inherited from `g.LinearObject` that would have inherited from `g.GeometryObject`. + // - Any number of ancestors may be provided. + // - Note that neither `obj` nor any of the arguments need to actually be prototypes of any JavaScript quasiclass, that was just a simplified explanation. + // - We can create a new object composed from the properties of any number of other objects (since they do not have a constructor, we can think of those as interfaces). + // - `extend({ a: 1, b: 2 }, { b: 10, c: 20 }, { c: 100, d: 200 })` gives `{ a: 1, b: 10, c: 100, d: 200 }`. + // - Basically, with this function, we can emulate the `extends` keyword as well as the `implements` keyword. + // - Therefore, both of the following are valid: + // - `Lineto.prototype = extend(Line.prototype, segmentPrototype, linetoPrototype)` + // - `Moveto.prototype = extend(segmentPrototype, movetoPrototype)` + + var i; + var n; + + var args = []; + n = arguments.length; + for (i = 1; i < n; i++) { // skip over obj + args.push(arguments[i]); + } + + if (!obj) throw new Error('Missing a parent object.'); + var child = Object.create(obj); + + n = args.length; + for (i = 0; i < n; i++) { + + var src = args[i]; + + var inheritedProperty; + var key; + for (key in src) { + + if (src.hasOwnProperty(key)) { + delete child[key]; // delete property inherited from parent + inheritedProperty = Object.getOwnPropertyDescriptor(src, key); // get new definition of property from src + Object.defineProperty(child, key, inheritedProperty); // re-add property with new definition (includes getter/setter methods) + } + } + } + + return child; + } + + // Path segment interface: + var segmentPrototype = { + + // Redirect calls to closestPointNormalizedLength() function if closestPointT() is not defined for segment. + closestPointT: function(p) { + + if (this.closestPointNormalizedLength) return this.closestPointNormalizedLength(p); + + throw new Error('Neither closestPointT() nor closestPointNormalizedLength() function is implemented.'); + }, + + isSegment: true, + + isSubpathStart: false, // true for Moveto segments + + isVisible: true, // false for Moveto segments + + nextSegment: null, // needed for subpath start segment updating + + // Return a fraction of result of length() function if lengthAtT() is not defined for segment. + lengthAtT: function(t) { + + if (t <= 0) return 0; + + var length = this.length(); + + if (t >= 1) return length; + + return length * t; + }, + + // Redirect calls to pointAt() function if pointAtT() is not defined for segment. + pointAtT: function(t) { + + if (this.pointAt) return this.pointAt(t); + + throw new Error('Neither pointAtT() nor pointAt() function is implemented.'); + }, + + previousSegment: null, // needed to get segment start property + + subpathStartSegment: null, // needed to get closepath segment end property + + // Redirect calls to tangentAt() function if tangentAtT() is not defined for segment. + tangentAtT: function(t) { + + if (this.tangentAt) return this.tangentAt(t); + + throw new Error('Neither tangentAtT() nor tangentAt() function is implemented.'); + }, + + // VIRTUAL PROPERTIES (must be overriden by actual Segment implementations): + + // type + + // start // getter, always throws error for Moveto + + // end // usually directly assigned, getter for Closepath + + bbox: function() { + + throw new Error('Declaration missing for virtual function.'); + }, + + clone: function() { + + throw new Error('Declaration missing for virtual function.'); + }, + + closestPoint: function() { + + throw new Error('Declaration missing for virtual function.'); + }, + + closestPointLength: function() { + + throw new Error('Declaration missing for virtual function.'); + }, + + closestPointNormalizedLength: function() { + + throw new Error('Declaration missing for virtual function.'); + }, + + closestPointTangent: function() { + + throw new Error('Declaration missing for virtual function.'); + }, + + equals: function() { + + throw new Error('Declaration missing for virtual function.'); + }, + + getSubdivisions: function() { + + throw new Error('Declaration missing for virtual function.'); + }, + + isDifferentiable: function() { + + throw new Error('Declaration missing for virtual function.'); + }, + + length: function() { + + throw new Error('Declaration missing for virtual function.'); + }, + + pointAt: function() { + + throw new Error('Declaration missing for virtual function.'); + }, + + pointAtLength: function() { + + throw new Error('Declaration missing for virtual function.'); + }, + + scale: function() { + + throw new Error('Declaration missing for virtual function.'); + }, + + tangentAt: function() { + + throw new Error('Declaration missing for virtual function.'); + }, + + tangentAtLength: function() { + + throw new Error('Declaration missing for virtual function.'); + }, + + translate: function() { + + throw new Error('Declaration missing for virtual function.'); + }, + + serialize: function() { + + throw new Error('Declaration missing for virtual function.'); + }, + + toString: function() { + + throw new Error('Declaration missing for virtual function.'); + } + }; + + // Path segment implementations: + var Lineto = function() { + + var args = []; + var n = arguments.length; + for (var i = 0; i < n; i++) { + args.push(arguments[i]); + } + + if (!(this instanceof Lineto)) { // switching context of `this` to Lineto when called without `new` + return applyToNew(Lineto, args); + } + + if (n === 0) { + throw new Error('Lineto constructor expects 1 point or 2 coordinates (none provided).'); + } + + var outputArray; + + if (typeof args[0] === 'string' || typeof args[0] === 'number') { // coordinates provided + if (n === 2) { + this.end = new Point(+args[0], +args[1]); + return this; + + } else if (n < 2) { + throw new Error('Lineto constructor expects 1 point or 2 coordinates (' + n + ' coordinates provided).'); + + } else { // this is a poly-line segment + var segmentCoords; + outputArray = []; + for (i = 0; i < n; i += 2) { // coords come in groups of two + + segmentCoords = args.slice(i, i + 2); // will send one coord if args.length not divisible by 2 + outputArray.push(applyToNew(Lineto, segmentCoords)); + } + return outputArray; + } + + } else { // points provided + if (n === 1) { + this.end = new Point(args[0]); + return this; + + } else { // this is a poly-line segment + var segmentPoint; + outputArray = []; + for (i = 0; i < n; i += 1) { + + segmentPoint = args[i]; + outputArray.push(new Lineto(segmentPoint)); + } + return outputArray; + } + } + }; + + var linetoPrototype = { + + clone: function() { + + return new Lineto(this.end); + }, + + getSubdivisions: function() { + + return []; + }, + + isDifferentiable: function() { + + if (!this.previousSegment) return false; + + return !this.start.equals(this.end); + }, + + scale: function(sx, sy, origin) { + + this.end.scale(sx, sy, origin); + return this; + }, + + translate: function(tx, ty) { + + this.end.translate(tx, ty); + return this; + }, + + type: 'L', + + serialize: function() { + + var end = this.end; + return this.type + ' ' + end.x + ' ' + end.y; + }, + + toString: function() { + + return this.type + ' ' + this.start + ' ' + this.end; + } + }; + + Object.defineProperty(linetoPrototype, 'start', { + // get a reference to the end point of previous segment + + configurable: true, + + enumerable: true, + + get: function() { + + if (!this.previousSegment) throw new Error('Missing previous segment. (This segment cannot be the first segment of a path; OR segment has not yet been added to a path.)'); + + return this.previousSegment.end; + } + }); + + Lineto.prototype = extend(segmentPrototype, Line.prototype, linetoPrototype); + + var Curveto = function() { + + var args = []; + var n = arguments.length; + for (var i = 0; i < n; i++) { + args.push(arguments[i]); + } + + if (!(this instanceof Curveto)) { // switching context of `this` to Curveto when called without `new` + return applyToNew(Curveto, args); + } + + if (n === 0) { + throw new Error('Curveto constructor expects 3 points or 6 coordinates (none provided).'); + } + + var outputArray; + + if (typeof args[0] === 'string' || typeof args[0] === 'number') { // coordinates provided + if (n === 6) { + this.controlPoint1 = new Point(+args[0], +args[1]); + this.controlPoint2 = new Point(+args[2], +args[3]); + this.end = new Point(+args[4], +args[5]); + return this; + + } else if (n < 6) { + throw new Error('Curveto constructor expects 3 points or 6 coordinates (' + n + ' coordinates provided).'); + + } else { // this is a poly-bezier segment + var segmentCoords; + outputArray = []; + for (i = 0; i < n; i += 6) { // coords come in groups of six + + segmentCoords = args.slice(i, i + 6); // will send fewer than six coords if args.length not divisible by 6 + outputArray.push(applyToNew(Curveto, segmentCoords)); + } + return outputArray; + } + + } else { // points provided + if (n === 3) { + this.controlPoint1 = new Point(args[0]); + this.controlPoint2 = new Point(args[1]); + this.end = new Point(args[2]); + return this; + + } else if (n < 3) { + throw new Error('Curveto constructor expects 3 points or 6 coordinates (' + n + ' points provided).'); + + } else { // this is a poly-bezier segment + var segmentPoints; + outputArray = []; + for (i = 0; i < n; i += 3) { // points come in groups of three + + segmentPoints = args.slice(i, i + 3); // will send fewer than three points if args.length is not divisible by 3 + outputArray.push(applyToNew(Curveto, segmentPoints)); + } + return outputArray; + } + } + }; + + var curvetoPrototype = { + + clone: function() { + + return new Curveto(this.controlPoint1, this.controlPoint2, this.end); + }, + + isDifferentiable: function() { + + if (!this.previousSegment) return false; + + var start = this.start; + var control1 = this.controlPoint1; + var control2 = this.controlPoint2; + var end = this.end; + + return !(start.equals(control1) && control1.equals(control2) && control2.equals(end)); + }, + + scale: function(sx, sy, origin) { + + this.controlPoint1.scale(sx, sy, origin); + this.controlPoint2.scale(sx, sy, origin); + this.end.scale(sx, sy, origin); + return this; + }, + + translate: function(tx, ty) { + + this.controlPoint1.translate(tx, ty); + this.controlPoint2.translate(tx, ty); + this.end.translate(tx, ty); + return this; + }, + + type: 'C', + + serialize: function() { + + var c1 = this.controlPoint1; + var c2 = this.controlPoint2; + var end = this.end; + return this.type + ' ' + c1.x + ' ' + c1.y + ' ' + c2.x + ' ' + c2.y + ' ' + end.x + ' ' + end.y; + }, + + toString: function() { + + return this.type + ' ' + this.start + ' ' + this.controlPoint1 + ' ' + this.controlPoint2 + ' ' + this.end; + } + }; + + Object.defineProperty(curvetoPrototype, 'start', { + // get a reference to the end point of previous segment + + configurable: true, + + enumerable: true, + + get: function() { + + if (!this.previousSegment) throw new Error('Missing previous segment. (This segment cannot be the first segment of a path; OR segment has not yet been added to a path.)'); + + return this.previousSegment.end; + } + }); + + Curveto.prototype = extend(segmentPrototype, Curve.prototype, curvetoPrototype); + + var Moveto = function() { + + var args = []; + var n = arguments.length; + for (var i = 0; i < n; i++) { + args.push(arguments[i]); + } + + if (!(this instanceof Moveto)) { // switching context of `this` to Moveto when called without `new` + return applyToNew(Moveto, args); + } + + if (n === 0) { + throw new Error('Moveto constructor expects 1 point or 2 coordinates (none provided).'); + } + + var outputArray; + + if (typeof args[0] === 'string' || typeof args[0] === 'number') { // coordinates provided + if (n === 2) { + this.end = new Point(+args[0], +args[1]); + return this; + + } else if (n < 2) { + throw new Error('Moveto constructor expects 1 point or 2 coordinates (' + n + ' coordinates provided).'); + + } else { // this is a moveto-with-subsequent-poly-line segment + var segmentCoords; + outputArray = []; + for (i = 0; i < n; i += 2) { // coords come in groups of two + + segmentCoords = args.slice(i, i + 2); // will send one coord if args.length not divisible by 2 + if (i === 0) outputArray.push(applyToNew(Moveto, segmentCoords)); + else outputArray.push(applyToNew(Lineto, segmentCoords)); + } + return outputArray; + } + + } else { // points provided + if (n === 1) { + this.end = new Point(args[0]); + return this; + + } else { // this is a moveto-with-subsequent-poly-line segment + var segmentPoint; + outputArray = []; + for (i = 0; i < n; i += 1) { // points come one by one + + segmentPoint = args[i]; + if (i === 0) outputArray.push(new Moveto(segmentPoint)); + else outputArray.push(new Lineto(segmentPoint)); + } + return outputArray; + } + } + }; + + var movetoPrototype = { + + bbox: function() { + + return null; + }, + + clone: function() { + + return new Moveto(this.end); + }, + + closestPoint: function() { + + return this.end.clone(); + }, + + closestPointNormalizedLength: function() { + + return 0; + }, + + closestPointLength: function() { + + return 0; + }, + + closestPointT: function() { + + return 1; + }, + + closestPointTangent: function() { + + return null; + }, + + equals: function(m) { + + return this.end.equals(m.end); + }, + + getSubdivisions: function() { + + return []; + }, + + isDifferentiable: function() { + + return false; + }, + + isSubpathStart: true, + + isVisible: false, + + length: function() { + + return 0; + }, + + lengthAtT: function() { + + return 0; + }, + + pointAt: function() { + + return this.end.clone(); + }, + + pointAtLength: function() { + + return this.end.clone(); + }, + + pointAtT: function() { + + return this.end.clone(); + }, + + scale: function(sx, sy, origin) { + + this.end.scale(sx, sy, origin); + return this; + }, + + tangentAt: function() { + + return null; + }, + + tangentAtLength: function() { + + return null; + }, + + tangentAtT: function() { + + return null; + }, + + translate: function(tx, ty) { + + this.end.translate(tx, ty); + return this; + }, + + type: 'M', + + serialize: function() { + + var end = this.end; + return this.type + ' ' + end.x + ' ' + end.y; + }, + + toString: function() { + + return this.type + ' ' + this.end; + } + }; + + Object.defineProperty(movetoPrototype, 'start', { + + configurable: true, + + enumerable: true, + + get: function() { + + throw new Error('Illegal access. Moveto segments should not need a start property.'); + } + }) + + Moveto.prototype = extend(segmentPrototype, movetoPrototype); // does not inherit from any other geometry object + + var Closepath = function() { + + var args = []; + var n = arguments.length; + for (var i = 0; i < n; i++) { + args.push(arguments[i]); + } + + if (!(this instanceof Closepath)) { // switching context of `this` to Closepath when called without `new` + return applyToNew(Closepath, args); + } + + if (n > 0) { + throw new Error('Closepath constructor expects no arguments.'); + } + + return this; + }; + + var closepathPrototype = { + + clone: function() { + + return new Closepath(); + }, + + getSubdivisions: function() { + + return []; + }, + + isDifferentiable: function() { + + if (!this.previousSegment || !this.subpathStartSegment) return false; + + return !this.start.equals(this.end); + }, + + scale: function() { + + return this; + }, + + translate: function() { + + return this; + }, + + type: 'Z', + + serialize: function() { + + return this.type; + }, + + toString: function() { + + return this.type + ' ' + this.start + ' ' + this.end; + } + }; + + Object.defineProperty(closepathPrototype, 'start', { + // get a reference to the end point of previous segment + + configurable: true, + + enumerable: true, + + get: function() { + + if (!this.previousSegment) throw new Error('Missing previous segment. (This segment cannot be the first segment of a path; OR segment has not yet been added to a path.)'); + + return this.previousSegment.end; + } + }); + + Object.defineProperty(closepathPrototype, 'end', { + // get a reference to the end point of subpath start segment + + configurable: true, + + enumerable: true, + + get: function() { + + if (!this.subpathStartSegment) throw new Error('Missing subpath start segment. (This segment needs a subpath start segment (e.g. Moveto); OR segment has not yet been added to a path.)'); + + return this.subpathStartSegment.end; + } + }) + + Closepath.prototype = extend(segmentPrototype, Line.prototype, closepathPrototype); + + var segmentTypes = Path.segmentTypes = { + L: Lineto, + C: Curveto, + M: Moveto, + Z: Closepath, + z: Closepath + }; + + Path.regexSupportedData = new RegExp('^[\\s\\d' + Object.keys(segmentTypes).join('') + ',.]*$'); + + Path.isDataSupported = function(d) { + if (typeof d !== 'string') return false; + return this.regexSupportedData.test(d); + } + + return g; + +})(); + +// Vectorizer. +// ----------- + +// A tiny library for making your life easier when dealing with SVG. +// The only Vectorizer dependency is the Geometry library. + +var V; +var Vectorizer; + +V = Vectorizer = (function() { + + 'use strict'; + + var hasSvg = typeof window === 'object' && + !!( + window.SVGAngle || + document.implementation.hasFeature('http://www.w3.org/TR/SVG11/feature#BasicStructure', '1.1') + ); + + // SVG support is required. + if (!hasSvg) { + + // Return a function that throws an error when it is used. + return function() { + throw new Error('SVG is required to use Vectorizer.'); + }; + } + + // XML namespaces. + var ns = { + xmlns: 'http://www.w3.org/2000/svg', + xml: 'http://www.w3.org/XML/1998/namespace', + xlink: 'http://www.w3.org/1999/xlink', + xhtml: 'http://www.w3.org/1999/xhtml' + }; + + var SVGversion = '1.1'; + + // Declare shorthands to the most used math functions. + var math = Math; + var PI = math.PI; + var atan2 = math.atan2; + var sqrt = math.sqrt; + var min = math.min; + var max = math.max; + var cos = math.cos; + var sin = math.sin; + + var V = function(el, attrs, children) { + + // This allows using V() without the new keyword. + if (!(this instanceof V)) { + return V.apply(Object.create(V.prototype), arguments); + } + + if (!el) return; + + if (V.isV(el)) { + el = el.node; + } + + attrs = attrs || {}; + + if (V.isString(el)) { + + if (el.toLowerCase() === 'svg') { + + // Create a new SVG canvas. + el = V.createSvgDocument(); + + } else if (el[0] === '<') { + + // Create element from an SVG string. + // Allows constructs of type: `document.appendChild(V('').node)`. + + var svgDoc = V.createSvgDocument(el); + + // Note that `V()` might also return an array should the SVG string passed as + // the first argument contain more than one root element. + if (svgDoc.childNodes.length > 1) { + + // Map child nodes to `V`s. + var arrayOfVels = []; + var i, len; + + for (i = 0, len = svgDoc.childNodes.length; i < len; i++) { + + var childNode = svgDoc.childNodes[i]; + arrayOfVels.push(new V(document.importNode(childNode, true))); + } + + return arrayOfVels; + } + + el = document.importNode(svgDoc.firstChild, true); + + } else { + + el = document.createElementNS(ns.xmlns, el); + } + + V.ensureId(el); + } + + this.node = el; + + this.setAttributes(attrs); + + if (children) { + this.append(children); + } + + return this; + }; + + var VPrototype = V.prototype; + + Object.defineProperty(VPrototype, 'id', { + enumerable: true, + get: function() { + return this.node.id; + }, + set: function(id) { + this.node.id = id; + } + }); + + /** + * @param {SVGGElement} toElem + * @returns {SVGMatrix} + */ + VPrototype.getTransformToElement = function(toElem) { + toElem = V.toNode(toElem); + return toElem.getScreenCTM().inverse().multiply(this.node.getScreenCTM()); + }; + + /** + * @param {SVGMatrix} matrix + * @param {Object=} opt + * @returns {Vectorizer|SVGMatrix} Setter / Getter + */ + VPrototype.transform = function(matrix, opt) { + + var node = this.node; + if (V.isUndefined(matrix)) { + return V.transformStringToMatrix(this.attr('transform')); + } + + if (opt && opt.absolute) { + return this.attr('transform', V.matrixToTransformString(matrix)); + } + + var svgTransform = V.createSVGTransform(matrix); + node.transform.baseVal.appendItem(svgTransform); + return this; + }; + + VPrototype.translate = function(tx, ty, opt) { + + opt = opt || {}; + ty = ty || 0; + + var transformAttr = this.attr('transform') || ''; + var transform = V.parseTransformString(transformAttr); + transformAttr = transform.value; + // Is it a getter? + if (V.isUndefined(tx)) { + return transform.translate; + } + + transformAttr = transformAttr.replace(/translate\([^\)]*\)/g, '').trim(); + + var newTx = opt.absolute ? tx : transform.translate.tx + tx; + var newTy = opt.absolute ? ty : transform.translate.ty + ty; + var newTranslate = 'translate(' + newTx + ',' + newTy + ')'; + + // Note that `translate()` is always the first transformation. This is + // usually the desired case. + this.attr('transform', (newTranslate + ' ' + transformAttr).trim()); + return this; + }; + + VPrototype.rotate = function(angle, cx, cy, opt) { + + opt = opt || {}; + + var transformAttr = this.attr('transform') || ''; + var transform = V.parseTransformString(transformAttr); + transformAttr = transform.value; + + // Is it a getter? + if (V.isUndefined(angle)) { + return transform.rotate; + } + + transformAttr = transformAttr.replace(/rotate\([^\)]*\)/g, '').trim(); + + angle %= 360; + + var newAngle = opt.absolute ? angle : transform.rotate.angle + angle; + var newOrigin = (cx !== undefined && cy !== undefined) ? ',' + cx + ',' + cy : ''; + var newRotate = 'rotate(' + newAngle + newOrigin + ')'; + + this.attr('transform', (transformAttr + ' ' + newRotate).trim()); + return this; + }; + + // Note that `scale` as the only transformation does not combine with previous values. + VPrototype.scale = function(sx, sy) { + + sy = V.isUndefined(sy) ? sx : sy; + + var transformAttr = this.attr('transform') || ''; + var transform = V.parseTransformString(transformAttr); + transformAttr = transform.value; + + // Is it a getter? + if (V.isUndefined(sx)) { + return transform.scale; + } + + transformAttr = transformAttr.replace(/scale\([^\)]*\)/g, '').trim(); + + var newScale = 'scale(' + sx + ',' + sy + ')'; + + this.attr('transform', (transformAttr + ' ' + newScale).trim()); + return this; + }; + + // Get SVGRect that contains coordinates and dimension of the real bounding box, + // i.e. after transformations are applied. + // If `target` is specified, bounding box will be computed relatively to `target` element. + VPrototype.bbox = function(withoutTransformations, target) { + + var box; + var node = this.node; + var ownerSVGElement = node.ownerSVGElement; + + // If the element is not in the live DOM, it does not have a bounding box defined and + // so fall back to 'zero' dimension element. + if (!ownerSVGElement) { + return new g.Rect(0, 0, 0, 0); + } + + try { + + box = node.getBBox(); + + } catch (e) { + + // Fallback for IE. + box = { + x: node.clientLeft, + y: node.clientTop, + width: node.clientWidth, + height: node.clientHeight + }; + } + + if (withoutTransformations) { + return new g.Rect(box); + } + + var matrix = this.getTransformToElement(target || ownerSVGElement); + + return V.transformRect(box, matrix); + }; + + // Returns an SVGRect that contains coordinates and dimensions of the real bounding box, + // i.e. after transformations are applied. + // Fixes a browser implementation bug that returns incorrect bounding boxes for groups of svg elements. + // Takes an (Object) `opt` argument (optional) with the following attributes: + // (Object) `target` (optional): if not undefined, transform bounding boxes relative to `target`; if undefined, transform relative to this + // (Boolean) `recursive` (optional): if true, recursively enter all groups and get a union of element bounding boxes (svg bbox fix); if false or undefined, return result of native function this.node.getBBox(); + VPrototype.getBBox = function(opt) { + + var options = {}; + + var outputBBox; + var node = this.node; + var ownerSVGElement = node.ownerSVGElement; + + // If the element is not in the live DOM, it does not have a bounding box defined and + // so fall back to 'zero' dimension element. + if (!ownerSVGElement) { + return new g.Rect(0, 0, 0, 0); + } + + if (opt) { + if (opt.target) { // check if target exists + options.target = V.toNode(opt.target); // works for V objects, jquery objects, and node objects + } + if (opt.recursive) { + options.recursive = opt.recursive; + } + } + + if (!options.recursive) { + try { + outputBBox = node.getBBox(); + } catch (e) { + // Fallback for IE. + outputBBox = { + x: node.clientLeft, + y: node.clientTop, + width: node.clientWidth, + height: node.clientHeight + }; + } + + if (!options.target) { + // transform like this (that is, not at all) + return new g.Rect(outputBBox); + } else { + // transform like target + var matrix = this.getTransformToElement(options.target); + return V.transformRect(outputBBox, matrix); + } + } else { // if we want to calculate the bbox recursively + // browsers report correct bbox around svg elements (one that envelops the path lines tightly) + // but some browsers fail to report the same bbox when the elements are in a group (returning a looser bbox that also includes control points, like node.getClientRect()) + // this happens even if we wrap a single svg element into a group! + // this option setting makes the function recursively enter all the groups from this and deeper, get bboxes of the elements inside, then return a union of those bboxes + + var children = this.children(); + var n = children.length; + + if (n === 0) { + return this.getBBox({ target: options.target, recursive: false }); + } + + // recursion's initial pass-through setting: + // recursive passes-through just keep the target as whatever was set up here during the initial pass-through + if (!options.target) { + // transform children/descendants like this (their parent/ancestor) + options.target = this; + } // else transform children/descendants like target + + for (var i = 0; i < n; i++) { + var currentChild = children[i]; + + var childBBox; + + // if currentChild is not a group element, get its bbox with a nonrecursive call + if (currentChild.children().length === 0) { + childBBox = currentChild.getBBox({ target: options.target, recursive: false }); + } + else { + // if currentChild is a group element (determined by checking the number of children), enter it with a recursive call + childBBox = currentChild.getBBox({ target: options.target, recursive: true }); + } + + if (!outputBBox) { + // if this is the first iteration + outputBBox = childBBox; + } else { + // make a new bounding box rectangle that contains this child's bounding box and previous bounding box + outputBBox = outputBBox.union(childBBox); + } + } + + return outputBBox; + } + }; + + // Text() helpers + + function createTextPathNode(attrs, vel) { + attrs || (attrs = {}); + var textPathElement = V('textPath'); + var d = attrs.d; + if (d && attrs['xlink:href'] === undefined) { + // If `opt.attrs` is a plain string, consider it to be directly the + // SVG path data for the text to go along (this is a shortcut). + // Otherwise if it is an object and contains the `d` property, then this is our path. + // Wrap the text in the SVG element that points + // to a path defined by `opt.attrs` inside the `` element. + var linkedPath = V('path').attr('d', d).appendTo(vel.defs()); + textPathElement.attr('xlink:href', '#' + linkedPath.id); + } + if (V.isObject(attrs)) { + // Set attributes on the ``. The most important one + // is the `xlink:href` that points to our newly created `` element in ``. + // Note that we also allow the following construct: + // `t.text('my text', { textPath: { 'xlink:href': '#my-other-path' } })`. + // In other words, one can completely skip the auto-creation of the path + // and use any other arbitrary path that is in the document. + textPathElement.attr(attrs); + } + return textPathElement.node; + } + + function annotateTextLine(lineNode, lineAnnotations, opt) { + opt || (opt = {}); + var includeAnnotationIndices = opt.includeAnnotationIndices; + var eol = opt.eol; + var lineHeight = opt.lineHeight; + var baseSize = opt.baseSize; + var maxFontSize = 0; + var fontMetrics = {}; + var lastJ = lineAnnotations.length - 1; + for (var j = 0; j <= lastJ; j++) { + var annotation = lineAnnotations[j]; + var fontSize = null; + if (V.isObject(annotation)) { + var annotationAttrs = annotation.attrs; + var vTSpan = V('tspan', annotationAttrs); + var tspanNode = vTSpan.node; + var t = annotation.t; + if (eol && j === lastJ) t += eol; + tspanNode.textContent = t; + // Per annotation className + var annotationClass = annotationAttrs['class']; + if (annotationClass) vTSpan.addClass(annotationClass); + // If `opt.includeAnnotationIndices` is `true`, + // set the list of indices of all the applied annotations + // in the `annotations` attribute. This list is a comma + // separated list of indices. + if (includeAnnotationIndices) vTSpan.attr('annotations', annotation.annotations); + // Check for max font size + fontSize = parseFloat(annotationAttrs['font-size']); + if (fontSize === undefined) fontSize = baseSize; + if (fontSize && fontSize > maxFontSize) maxFontSize = fontSize; + } else { + if (eol && j === lastJ) annotation += eol; + tspanNode = document.createTextNode(annotation || ' '); + if (baseSize && baseSize > maxFontSize) maxFontSize = baseSize; + } + lineNode.appendChild(tspanNode); + } + + if (maxFontSize) fontMetrics.maxFontSize = maxFontSize; + if (lineHeight) { + fontMetrics.lineHeight = lineHeight; + } else if (maxFontSize) { + fontMetrics.lineHeight = (maxFontSize * 1.2); + } + return fontMetrics; + } + + var emRegex = /em$/; + + function convertEmToPx(em, fontSize) { + var numerical = parseFloat(em); + if (emRegex.test(em)) return numerical * fontSize; + return numerical; + } + + function calculateDY(alignment, linesMetrics, baseSizePx, lineHeight) { + if (!Array.isArray(linesMetrics)) return 0; + var n = linesMetrics.length; + if (!n) return 0; + var lineMetrics = linesMetrics[0]; + var flMaxFont = convertEmToPx(lineMetrics.maxFontSize, baseSizePx) || baseSizePx; + var rLineHeights = 0; + var lineHeightPx = convertEmToPx(lineHeight, baseSizePx); + for (var i = 1; i < n; i++) { + lineMetrics = linesMetrics[i]; + var iLineHeight = convertEmToPx(lineMetrics.lineHeight, baseSizePx) || lineHeightPx; + rLineHeights += iLineHeight; + } + var llMaxFont = convertEmToPx(lineMetrics.maxFontSize, baseSizePx) || baseSizePx; + var dy; + switch (alignment) { + case 'middle': + dy = (flMaxFont / 2) - (0.15 * llMaxFont) - (rLineHeights / 2); + break; + case 'bottom': + dy = -(0.25 * llMaxFont) - rLineHeights; + break; + default: + case 'top': + dy = (0.8 * flMaxFont) + break; + } + return dy; + } + + VPrototype.text = function(content, opt) { + + if (content && typeof content !== 'string') throw new Error('Vectorizer: text() expects the first argument to be a string.'); + + // Replace all spaces with the Unicode No-break space (http://www.fileformat.info/info/unicode/char/a0/index.htm). + // IE would otherwise collapse all spaces into one. + content = V.sanitizeText(content); + opt || (opt = {}); + + // End of Line character + var eol = opt.eol; + // Text along path + var textPath = opt.textPath + // Vertical shift + var verticalAnchor = opt.textVerticalAnchor; + var namedVerticalAnchor = (verticalAnchor === 'middle' || verticalAnchor === 'bottom' || verticalAnchor === 'top'); + // Horizontal shift applied to all the lines but the first. + var x = opt.x; + if (x === undefined) x = this.attr('x') || 0; + // Annotations + var iai = opt.includeAnnotationIndices; + var annotations = opt.annotations; + if (annotations && !V.isArray(annotations)) annotations = [annotations]; + // Shift all the but first by one line (`1em`) + var defaultLineHeight = opt.lineHeight; + var autoLineHeight = (defaultLineHeight === 'auto'); + var lineHeight = (autoLineHeight) ? '1.5em' : (defaultLineHeight || '1em'); + // Clearing the element + this.empty(); + this.attr({ + // Preserve spaces. In other words, we do not want consecutive spaces to get collapsed to one. + 'xml:space': 'preserve', + // An empty text gets rendered into the DOM in webkit-based browsers. + // In order to unify this behaviour across all browsers + // we rather hide the text element when it's empty. + 'display': (content) ? null : 'none' + }); + // Set default font-size if none + var fontSize = parseFloat(this.attr('font-size')); + if (!fontSize) { + fontSize = 16; + if (namedVerticalAnchor || annotations) this.attr('font-size', fontSize); + } + + var doc = document; + var containerNode; + if (textPath) { + // Now all the ``s will be inside the ``. + if (typeof textPath === 'string') textPath = { d: textPath }; + containerNode = createTextPathNode(textPath, this); + } else { + containerNode = doc.createDocumentFragment(); + } + var offset = 0; + var lines = content.split('\n'); + var linesMetrics = []; + var annotatedY; + for (var i = 0, lastI = lines.length - 1; i <= lastI; i++) { + var dy = lineHeight; + var lineClassName = 'v-line'; + var lineNode = doc.createElementNS(V.namespace.xmlns, 'tspan'); + var line = lines[i]; + var lineMetrics; + if (line) { + if (annotations) { + // Find the *compacted* annotations for this line. + var lineAnnotations = V.annotateString(line, annotations, { + offset: -offset, + includeAnnotationIndices: iai + }); + lineMetrics = annotateTextLine(lineNode, lineAnnotations, { + includeAnnotationIndices: iai, + eol: (i !== lastI && eol), + lineHeight: (autoLineHeight) ? null : lineHeight, + baseSize: fontSize + }); + // Get the line height based on the biggest font size in the annotations for this line. + var iLineHeight = lineMetrics.lineHeight; + if (iLineHeight && autoLineHeight && i !== 0) dy = iLineHeight; + if (i === 0) annotatedY = lineMetrics.maxFontSize * 0.8; + } else { + if (eol && i !== lastI) line += eol; + lineNode.textContent = line; + } + } else { + // Make sure the textContent is never empty. If it is, add a dummy + // character and make it invisible, making the following lines correctly + // relatively positioned. `dy=1em` won't work with empty lines otherwise. + lineNode.textContent = '-'; + lineClassName += ' v-empty-line'; + // 'opacity' needs to be specified with fill, stroke. Opacity without specification + // is not applied in Firefox + var lineNodeStyle = lineNode.style; + lineNodeStyle.fillOpacity = 0; + lineNodeStyle.strokeOpacity = 0; + if (annotations) lineMetrics = {}; + } + if (lineMetrics) linesMetrics.push(lineMetrics); + if (i > 0) lineNode.setAttribute('dy', dy); + // Firefox requires 'x' to be set on the first line when inside a text path + if (i > 0 || textPath) lineNode.setAttribute('x', x); + lineNode.className.baseVal = lineClassName; + containerNode.appendChild(lineNode); + offset += line.length + 1; // + 1 = newline character. + } + // Y Alignment calculation + if (namedVerticalAnchor) { + if (annotations) { + dy = calculateDY(verticalAnchor, linesMetrics, fontSize, lineHeight); + } else if (verticalAnchor === 'top') { + // A shortcut for top alignment. It does not depend on font-size nor line-height + dy = '0.8em'; + } else { + var rh; // remaining height + if (lastI > 0) { + rh = parseFloat(lineHeight) || 1; + rh *= lastI; + if (!emRegex.test(lineHeight)) rh /= fontSize; + } else { + // Single-line text + rh = 0; + } + switch (verticalAnchor) { + case 'middle': + dy = (0.3 - (rh / 2)) + 'em' + break; + case 'bottom': + dy = (-rh - 0.3) + 'em' + break; + } + } + } else { + if (verticalAnchor === 0) { + dy = '0em'; + } else if (verticalAnchor) { + dy = verticalAnchor; + } else { + // No vertical anchor is defined + dy = 0; + // Backwards compatibility - we change the `y` attribute instead of `dy`. + if (this.attr('y') === null) this.attr('y', annotatedY || '0.8em'); + } + } + containerNode.firstChild.setAttribute('dy', dy); + // Appending lines to the element. + this.append(containerNode); + return this; + }; + + /** + * @public + * @param {string} name + * @returns {Vectorizer} + */ + VPrototype.removeAttr = function(name) { + + var qualifiedName = V.qualifyAttr(name); + var el = this.node; + + if (qualifiedName.ns) { + if (el.hasAttributeNS(qualifiedName.ns, qualifiedName.local)) { + el.removeAttributeNS(qualifiedName.ns, qualifiedName.local); + } + } else if (el.hasAttribute(name)) { + el.removeAttribute(name); + } + return this; + }; + + VPrototype.attr = function(name, value) { + + if (V.isUndefined(name)) { + + // Return all attributes. + var attributes = this.node.attributes; + var attrs = {}; + + for (var i = 0; i < attributes.length; i++) { + attrs[attributes[i].name] = attributes[i].value; + } + + return attrs; + } + + if (V.isString(name) && V.isUndefined(value)) { + return this.node.getAttribute(name); + } + + if (typeof name === 'object') { + + for (var attrName in name) { + if (name.hasOwnProperty(attrName)) { + this.setAttribute(attrName, name[attrName]); + } + } + + } else { + + this.setAttribute(name, value); + } + + return this; + }; + + VPrototype.normalizePath = function() { + + var tagName = this.tagName(); + if (tagName === 'PATH') { + this.attr('d', V.normalizePathData(this.attr('d'))); + } + + return this; + } + + VPrototype.remove = function() { + + if (this.node.parentNode) { + this.node.parentNode.removeChild(this.node); + } + + return this; + }; + + VPrototype.empty = function() { + + while (this.node.firstChild) { + this.node.removeChild(this.node.firstChild); + } + + return this; + }; + + /** + * @private + * @param {object} attrs + * @returns {Vectorizer} + */ + VPrototype.setAttributes = function(attrs) { + + for (var key in attrs) { + if (attrs.hasOwnProperty(key)) { + this.setAttribute(key, attrs[key]); + } + } + + return this; + }; + + VPrototype.append = function(els) { + + if (!V.isArray(els)) { + els = [els]; + } + + for (var i = 0, len = els.length; i < len; i++) { + this.node.appendChild(V.toNode(els[i])); + } + + return this; + }; + + VPrototype.prepend = function(els) { + + var child = this.node.firstChild; + return child ? V(child).before(els) : this.append(els); + }; + + VPrototype.before = function(els) { + + var node = this.node; + var parent = node.parentNode; + + if (parent) { + + if (!V.isArray(els)) { + els = [els]; + } + + for (var i = 0, len = els.length; i < len; i++) { + parent.insertBefore(V.toNode(els[i]), node); + } + } + + return this; + }; + + VPrototype.appendTo = function(node) { + V.toNode(node).appendChild(this.node); + return this; + }, + + VPrototype.svg = function() { + + return this.node instanceof window.SVGSVGElement ? this : V(this.node.ownerSVGElement); + }; + + VPrototype.tagName = function() { + + return this.node.tagName.toUpperCase(); + }; + + VPrototype.defs = function() { + var context = this.svg() || this; + var defsNode = context.node.getElementsByTagName('defs')[0]; + if (defsNode) return V(defsNode); + return V('defs').appendTo(context); + }; + + VPrototype.clone = function() { + + var clone = V(this.node.cloneNode(true/* deep */)); + // Note that clone inherits also ID. Therefore, we need to change it here. + clone.node.id = V.uniqueId(); + return clone; + }; + + VPrototype.findOne = function(selector) { + + var found = this.node.querySelector(selector); + return found ? V(found) : undefined; + }; + + VPrototype.find = function(selector) { + + var vels = []; + var nodes = this.node.querySelectorAll(selector); + + if (nodes) { + + // Map DOM elements to `V`s. + for (var i = 0; i < nodes.length; i++) { + vels.push(V(nodes[i])); + } + } + + return vels; + }; + + // Returns an array of V elements made from children of this.node. + VPrototype.children = function() { + + var children = this.node.childNodes; + + var outputArray = []; + for (var i = 0; i < children.length; i++) { + var currentChild = children[i]; + if (currentChild.nodeType === 1) { + outputArray.push(V(children[i])); + } + } + return outputArray; + }; + + // Find an index of an element inside its container. + VPrototype.index = function() { + + var index = 0; + var node = this.node.previousSibling; + + while (node) { + // nodeType 1 for ELEMENT_NODE + if (node.nodeType === 1) index++; + node = node.previousSibling; + } + + return index; + }; + + VPrototype.findParentByClass = function(className, terminator) { + + var ownerSVGElement = this.node.ownerSVGElement; + var node = this.node.parentNode; + + while (node && node !== terminator && node !== ownerSVGElement) { + + var vel = V(node); + if (vel.hasClass(className)) { + return vel; + } + + node = node.parentNode; + } + + return null; + }; + + // https://jsperf.com/get-common-parent + VPrototype.contains = function(el) { + + var a = this.node; + var b = V.toNode(el); + var bup = b && b.parentNode; + + return (a === bup) || !!(bup && bup.nodeType === 1 && (a.compareDocumentPosition(bup) & 16)); + }; + + // Convert global point into the coordinate space of this element. + VPrototype.toLocalPoint = function(x, y) { + + var svg = this.svg().node; + + var p = svg.createSVGPoint(); + p.x = x; + p.y = y; + + try { + + var globalPoint = p.matrixTransform(svg.getScreenCTM().inverse()); + var globalToLocalMatrix = this.getTransformToElement(svg).inverse(); + + } catch (e) { + // IE9 throws an exception in odd cases. (`Unexpected call to method or property access`) + // We have to make do with the original coordianates. + return p; + } + + return globalPoint.matrixTransform(globalToLocalMatrix); + }; + + VPrototype.translateCenterToPoint = function(p) { + + var bbox = this.getBBox({ target: this.svg() }); + var center = bbox.center(); + + this.translate(p.x - center.x, p.y - center.y); + return this; + }; + + // Efficiently auto-orient an element. This basically implements the orient=auto attribute + // of markers. The easiest way of understanding on what this does is to imagine the element is an + // arrowhead. Calling this method on the arrowhead makes it point to the `position` point while + // being auto-oriented (properly rotated) towards the `reference` point. + // `target` is the element relative to which the transformations are applied. Usually a viewport. + VPrototype.translateAndAutoOrient = function(position, reference, target) { + + // Clean-up previously set transformations except the scale. If we didn't clean up the + // previous transformations then they'd add up with the old ones. Scale is an exception as + // it doesn't add up, consider: `this.scale(2).scale(2).scale(2)`. The result is that the + // element is scaled by the factor 2, not 8. + + var s = this.scale(); + this.attr('transform', ''); + this.scale(s.sx, s.sy); + + var svg = this.svg().node; + var bbox = this.getBBox({ target: target || svg }); + + // 1. Translate to origin. + var translateToOrigin = svg.createSVGTransform(); + translateToOrigin.setTranslate(-bbox.x - bbox.width / 2, -bbox.y - bbox.height / 2); + + // 2. Rotate around origin. + var rotateAroundOrigin = svg.createSVGTransform(); + var angle = (new g.Point(position)).changeInAngle(position.x - reference.x, position.y - reference.y, reference); + rotateAroundOrigin.setRotate(angle, 0, 0); + + // 3. Translate to the `position` + the offset (half my width) towards the `reference` point. + var translateFinal = svg.createSVGTransform(); + var finalPosition = (new g.Point(position)).move(reference, bbox.width / 2); + translateFinal.setTranslate(position.x + (position.x - finalPosition.x), position.y + (position.y - finalPosition.y)); + + // 4. Apply transformations. + var ctm = this.getTransformToElement(target || svg); + var transform = svg.createSVGTransform(); + transform.setMatrix( + translateFinal.matrix.multiply( + rotateAroundOrigin.matrix.multiply( + translateToOrigin.matrix.multiply( + ctm))) + ); + + // Instead of directly setting the `matrix()` transform on the element, first, decompose + // the matrix into separate transforms. This allows us to use normal Vectorizer methods + // as they don't work on matrices. An example of this is to retrieve a scale of an element. + // this.node.transform.baseVal.initialize(transform); + + var decomposition = V.decomposeMatrix(transform.matrix); + + this.translate(decomposition.translateX, decomposition.translateY); + this.rotate(decomposition.rotation); + // Note that scale has been already applied, hence the following line stays commented. (it's here just for reference). + //this.scale(decomposition.scaleX, decomposition.scaleY); + + return this; + }; + + VPrototype.animateAlongPath = function(attrs, path) { + + path = V.toNode(path); + + var id = V.ensureId(path); + var animateMotion = V('animateMotion', attrs); + var mpath = V('mpath', { 'xlink:href': '#' + id }); + + animateMotion.append(mpath); + + this.append(animateMotion); + try { + animateMotion.node.beginElement(); + } catch (e) { + // Fallback for IE 9. + // Run the animation programatically if FakeSmile (`http://leunen.me/fakesmile/`) present + if (document.documentElement.getAttribute('smiling') === 'fake') { + + // Register the animation. (See `https://answers.launchpad.net/smil/+question/203333`) + var animation = animateMotion.node; + animation.animators = []; + + var animationID = animation.getAttribute('id'); + if (animationID) id2anim[animationID] = animation; + + var targets = getTargets(animation); + for (var i = 0, len = targets.length; i < len; i++) { + var target = targets[i]; + var animator = new Animator(animation, target, i); + animators.push(animator); + animation.animators[i] = animator; + animator.register(); + } + } + } + return this; + }; + + VPrototype.hasClass = function(className) { + + return new RegExp('(\\s|^)' + className + '(\\s|$)').test(this.node.getAttribute('class')); + }; + + VPrototype.addClass = function(className) { + + if (!this.hasClass(className)) { + var prevClasses = this.node.getAttribute('class') || ''; + this.node.setAttribute('class', (prevClasses + ' ' + className).trim()); + } + + return this; + }; + + VPrototype.removeClass = function(className) { + + if (this.hasClass(className)) { + var newClasses = this.node.getAttribute('class').replace(new RegExp('(\\s|^)' + className + '(\\s|$)', 'g'), '$2'); + this.node.setAttribute('class', newClasses); + } + + return this; + }; + + VPrototype.toggleClass = function(className, toAdd) { + + var toRemove = V.isUndefined(toAdd) ? this.hasClass(className) : !toAdd; + + if (toRemove) { + this.removeClass(className); + } else { + this.addClass(className); + } + + return this; + }; + + // Interpolate path by discrete points. The precision of the sampling + // is controlled by `interval`. In other words, `sample()` will generate + // a point on the path starting at the beginning of the path going to the end + // every `interval` pixels. + // The sampler can be very useful for e.g. finding intersection between two + // paths (finding the two closest points from two samples). + VPrototype.sample = function(interval) { + + interval = interval || 1; + var node = this.node; + var length = node.getTotalLength(); + var samples = []; + var distance = 0; + var sample; + while (distance < length) { + sample = node.getPointAtLength(distance); + samples.push({ x: sample.x, y: sample.y, distance: distance }); + distance += interval; + } + return samples; + }; + + VPrototype.convertToPath = function() { + + var path = V('path'); + path.attr(this.attr()); + var d = this.convertToPathData(); + if (d) { + path.attr('d', d); + } + return path; + }; + + VPrototype.convertToPathData = function() { + + var tagName = this.tagName(); + + switch (tagName) { + case 'PATH': + return this.attr('d'); + case 'LINE': + return V.convertLineToPathData(this.node); + case 'POLYGON': + return V.convertPolygonToPathData(this.node); + case 'POLYLINE': + return V.convertPolylineToPathData(this.node); + case 'ELLIPSE': + return V.convertEllipseToPathData(this.node); + case 'CIRCLE': + return V.convertCircleToPathData(this.node); + case 'RECT': + return V.convertRectToPathData(this.node); + } + + throw new Error(tagName + ' cannot be converted to PATH.'); + }; + + V.prototype.toGeometryShape = function() { + var x, y, width, height, cx, cy, r, rx, ry, points, d; + switch (this.tagName()) { + + case 'RECT': + x = parseFloat(this.attr('x')) || 0; + y = parseFloat(this.attr('y')) || 0; + width = parseFloat(this.attr('width')) || 0; + height = parseFloat(this.attr('height')) || 0; + return new g.Rect(x, y, width, height); + + case 'CIRCLE': + cx = parseFloat(this.attr('cx')) || 0; + cy = parseFloat(this.attr('cy')) || 0; + r = parseFloat(this.attr('r')) || 0; + return new g.Ellipse({ x: cx, y: cy }, r, r); + + case 'ELLIPSE': + cx = parseFloat(this.attr('cx')) || 0; + cy = parseFloat(this.attr('cy')) || 0; + rx = parseFloat(this.attr('rx')) || 0; + ry = parseFloat(this.attr('ry')) || 0; + return new g.Ellipse({ x: cx, y: cy }, rx, ry); + + case 'POLYLINE': + points = V.getPointsFromSvgNode(this); + return new g.Polyline(points); + + case 'POLYGON': + points = V.getPointsFromSvgNode(this); + if (points.length > 1) points.push(points[0]); + return new g.Polyline(points); + + case 'PATH': + d = this.attr('d'); + if (!g.Path.isDataSupported(d)) d = V.normalizePathData(d); + return new g.Path(d); + + case 'LINE': + x1 = parseFloat(this.attr('x1')) || 0; + y1 = parseFloat(this.attr('y1')) || 0; + x2 = parseFloat(this.attr('x2')) || 0; + y2 = parseFloat(this.attr('y2')) || 0; + return new g.Line({ x: x1, y: y1 }, { x: x2, y: y2 }); + } + + // Anything else is a rectangle + return this.getBBox(); + }, + + // Find the intersection of a line starting in the center + // of the SVG `node` ending in the point `ref`. + // `target` is an SVG element to which `node`s transformations are relative to. + // In JointJS, `target` is the `paper.viewport` SVG group element. + // Note that `ref` point must be in the coordinate system of the `target` for this function to work properly. + // Returns a point in the `target` coordinte system (the same system as `ref` is in) if + // an intersection is found. Returns `undefined` otherwise. + VPrototype.findIntersection = function(ref, target) { + + var svg = this.svg().node; + target = target || svg; + var bbox = this.getBBox({ target: target }); + var center = bbox.center(); + + if (!bbox.intersectionWithLineFromCenterToPoint(ref)) return undefined; + + var spot; + var tagName = this.tagName(); + + // Little speed up optimalization for `` element. We do not do conversion + // to path element and sampling but directly calculate the intersection through + // a transformed geometrical rectangle. + if (tagName === 'RECT') { + + var gRect = new g.Rect( + parseFloat(this.attr('x') || 0), + parseFloat(this.attr('y') || 0), + parseFloat(this.attr('width')), + parseFloat(this.attr('height')) + ); + // Get the rect transformation matrix with regards to the SVG document. + var rectMatrix = this.getTransformToElement(target); + // Decompose the matrix to find the rotation angle. + var rectMatrixComponents = V.decomposeMatrix(rectMatrix); + // Now we want to rotate the rectangle back so that we + // can use `intersectionWithLineFromCenterToPoint()` passing the angle as the second argument. + var resetRotation = svg.createSVGTransform(); + resetRotation.setRotate(-rectMatrixComponents.rotation, center.x, center.y); + var rect = V.transformRect(gRect, resetRotation.matrix.multiply(rectMatrix)); + spot = (new g.Rect(rect)).intersectionWithLineFromCenterToPoint(ref, rectMatrixComponents.rotation); + + } else if (tagName === 'PATH' || tagName === 'POLYGON' || tagName === 'POLYLINE' || tagName === 'CIRCLE' || tagName === 'ELLIPSE') { + + var pathNode = (tagName === 'PATH') ? this : this.convertToPath(); + var samples = pathNode.sample(); + var minDistance = Infinity; + var closestSamples = []; + + var i, sample, gp, centerDistance, refDistance, distance; + + for (i = 0; i < samples.length; i++) { + + sample = samples[i]; + // Convert the sample point in the local coordinate system to the global coordinate system. + gp = V.createSVGPoint(sample.x, sample.y); + gp = gp.matrixTransform(this.getTransformToElement(target)); + sample = new g.Point(gp); + centerDistance = sample.distance(center); + // Penalize a higher distance to the reference point by 10%. + // This gives better results. This is due to + // inaccuracies introduced by rounding errors and getPointAtLength() returns. + refDistance = sample.distance(ref) * 1.1; + distance = centerDistance + refDistance; + + if (distance < minDistance) { + minDistance = distance; + closestSamples = [{ sample: sample, refDistance: refDistance }]; + } else if (distance < minDistance + 1) { + closestSamples.push({ sample: sample, refDistance: refDistance }); + } + } + + closestSamples.sort(function(a, b) { + return a.refDistance - b.refDistance; + }); + + if (closestSamples[0]) { + spot = closestSamples[0].sample; + } + } + + return spot; + }; + + /** + * @private + * @param {string} name + * @param {string} value + * @returns {Vectorizer} + */ + VPrototype.setAttribute = function(name, value) { + + var el = this.node; + + if (value === null) { + this.removeAttr(name); + return this; + } + + var qualifiedName = V.qualifyAttr(name); + + if (qualifiedName.ns) { + // Attribute names can be namespaced. E.g. `image` elements + // have a `xlink:href` attribute to set the source of the image. + el.setAttributeNS(qualifiedName.ns, name, value); + } else if (name === 'id') { + el.id = value; + } else { + el.setAttribute(name, value); + } + + return this; + }; + + // Create an SVG document element. + // If `content` is passed, it will be used as the SVG content of the `` root element. + V.createSvgDocument = function(content) { + + var svg = '' + (content || '') + ''; + var xml = V.parseXML(svg, { async: false }); + return xml.documentElement; + }; + + V.idCounter = 0; + + // A function returning a unique identifier for this client session with every call. + V.uniqueId = function() { + + return 'v-' + (++V.idCounter); + }; + + V.toNode = function(el) { + + return V.isV(el) ? el.node : (el.nodeName && el || el[0]); + }; + + V.ensureId = function(node) { + + node = V.toNode(node); + return node.id || (node.id = V.uniqueId()); + }; + + // Replace all spaces with the Unicode No-break space (http://www.fileformat.info/info/unicode/char/a0/index.htm). + // IE would otherwise collapse all spaces into one. This is used in the text() method but it is + // also exposed so that the programmer can use it in case he needs to. This is useful e.g. in tests + // when you want to compare the actual DOM text content without having to add the unicode character in + // the place of all spaces. + V.sanitizeText = function(text) { + + return (text || '').replace(/ /g, '\u00A0'); + }; + + V.isUndefined = function(value) { + + return typeof value === 'undefined'; + }; + + V.isString = function(value) { + + return typeof value === 'string'; + }; + + V.isObject = function(value) { + + return value && (typeof value === 'object'); + }; + + V.isArray = Array.isArray; + + V.parseXML = function(data, opt) { + + opt = opt || {}; + + var xml; + + try { + var parser = new DOMParser(); + + if (!V.isUndefined(opt.async)) { + parser.async = opt.async; + } + + xml = parser.parseFromString(data, 'text/xml'); + } catch (error) { + xml = undefined; + } + + if (!xml || xml.getElementsByTagName('parsererror').length) { + throw new Error('Invalid XML: ' + data); + } + + return xml; + }; + + /** + * @param {string} name + * @returns {{ns: string|null, local: string}} namespace and attribute name + */ + V.qualifyAttr = function(name) { + + if (name.indexOf(':') !== -1) { + var combinedKey = name.split(':'); + return { + ns: ns[combinedKey[0]], + local: combinedKey[1] + }; + } + + return { + ns: null, + local: name + }; + }; + + V.transformRegex = /(\w+)\(([^,)]+),?([^)]+)?\)/gi; + V.transformSeparatorRegex = /[ ,]+/; + V.transformationListRegex = /^(\w+)\((.*)\)/; + + V.transformStringToMatrix = function(transform) { + + var transformationMatrix = V.createSVGMatrix(); + var matches = transform && transform.match(V.transformRegex); + if (!matches) { + return transformationMatrix; + } + + for (var i = 0, n = matches.length; i < n; i++) { + var transformationString = matches[i]; + + var transformationMatch = transformationString.match(V.transformationListRegex); + if (transformationMatch) { + var sx, sy, tx, ty, angle; + var ctm = V.createSVGMatrix(); + var args = transformationMatch[2].split(V.transformSeparatorRegex); + switch (transformationMatch[1].toLowerCase()) { + case 'scale': + sx = parseFloat(args[0]); + sy = (args[1] === undefined) ? sx : parseFloat(args[1]); + ctm = ctm.scaleNonUniform(sx, sy); + break; + case 'translate': + tx = parseFloat(args[0]); + ty = parseFloat(args[1]); + ctm = ctm.translate(tx, ty); + break; + case 'rotate': + angle = parseFloat(args[0]); + tx = parseFloat(args[1]) || 0; + ty = parseFloat(args[2]) || 0; + if (tx !== 0 || ty !== 0) { + ctm = ctm.translate(tx, ty).rotate(angle).translate(-tx, -ty); + } else { + ctm = ctm.rotate(angle); + } + break; + case 'skewx': + angle = parseFloat(args[0]); + ctm = ctm.skewX(angle); + break; + case 'skewy': + angle = parseFloat(args[0]); + ctm = ctm.skewY(angle); + break; + case 'matrix': + ctm.a = parseFloat(args[0]); + ctm.b = parseFloat(args[1]); + ctm.c = parseFloat(args[2]); + ctm.d = parseFloat(args[3]); + ctm.e = parseFloat(args[4]); + ctm.f = parseFloat(args[5]); + break; + default: + continue; + } + + transformationMatrix = transformationMatrix.multiply(ctm); + } + + } + return transformationMatrix; + }; + + V.matrixToTransformString = function(matrix) { + matrix || (matrix = true); + + return 'matrix(' + + (matrix.a !== undefined ? matrix.a : 1) + ',' + + (matrix.b !== undefined ? matrix.b : 0) + ',' + + (matrix.c !== undefined ? matrix.c : 0) + ',' + + (matrix.d !== undefined ? matrix.d : 1) + ',' + + (matrix.e !== undefined ? matrix.e : 0) + ',' + + (matrix.f !== undefined ? matrix.f : 0) + + ')'; + }; + + V.parseTransformString = function(transform) { + + var translate, rotate, scale; + + if (transform) { + + var separator = V.transformSeparatorRegex; + + // Allow reading transform string with a single matrix + if (transform.trim().indexOf('matrix') >= 0) { + + var matrix = V.transformStringToMatrix(transform); + var decomposedMatrix = V.decomposeMatrix(matrix); + + translate = [decomposedMatrix.translateX, decomposedMatrix.translateY]; + scale = [decomposedMatrix.scaleX, decomposedMatrix.scaleY]; + rotate = [decomposedMatrix.rotation]; + + var transformations = []; + if (translate[0] !== 0 || translate[0] !== 0) { + transformations.push('translate(' + translate + ')'); + } + if (scale[0] !== 1 || scale[1] !== 1) { + transformations.push('scale(' + scale + ')'); + } + if (rotate[0] !== 0) { + transformations.push('rotate(' + rotate + ')'); + } + transform = transformations.join(' '); + + } else { + + var translateMatch = transform.match(/translate\((.*?)\)/); + if (translateMatch) { + translate = translateMatch[1].split(separator); + } + var rotateMatch = transform.match(/rotate\((.*?)\)/); + if (rotateMatch) { + rotate = rotateMatch[1].split(separator); + } + var scaleMatch = transform.match(/scale\((.*?)\)/); + if (scaleMatch) { + scale = scaleMatch[1].split(separator); + } + } + } + + var sx = (scale && scale[0]) ? parseFloat(scale[0]) : 1; + + return { + value: transform, + translate: { + tx: (translate && translate[0]) ? parseInt(translate[0], 10) : 0, + ty: (translate && translate[1]) ? parseInt(translate[1], 10) : 0 + }, + rotate: { + angle: (rotate && rotate[0]) ? parseInt(rotate[0], 10) : 0, + cx: (rotate && rotate[1]) ? parseInt(rotate[1], 10) : undefined, + cy: (rotate && rotate[2]) ? parseInt(rotate[2], 10) : undefined + }, + scale: { + sx: sx, + sy: (scale && scale[1]) ? parseFloat(scale[1]) : sx + } + }; + }; + + V.deltaTransformPoint = function(matrix, point) { + + var dx = point.x * matrix.a + point.y * matrix.c + 0; + var dy = point.x * matrix.b + point.y * matrix.d + 0; + return { x: dx, y: dy }; + }; + + V.decomposeMatrix = function(matrix) { + + // @see https://gist.github.com/2052247 + + // calculate delta transform point + var px = V.deltaTransformPoint(matrix, { x: 0, y: 1 }); + var py = V.deltaTransformPoint(matrix, { x: 1, y: 0 }); + + // calculate skew + var skewX = ((180 / PI) * atan2(px.y, px.x) - 90); + var skewY = ((180 / PI) * atan2(py.y, py.x)); + + return { + + translateX: matrix.e, + translateY: matrix.f, + scaleX: sqrt(matrix.a * matrix.a + matrix.b * matrix.b), + scaleY: sqrt(matrix.c * matrix.c + matrix.d * matrix.d), + skewX: skewX, + skewY: skewY, + rotation: skewX // rotation is the same as skew x + }; + }; + + // Return the `scale` transformation from the following equation: + // `translate(tx, ty) . rotate(angle) . scale(sx, sy) === matrix(a,b,c,d,e,f)` + V.matrixToScale = function(matrix) { + + var a,b,c,d; + if (matrix) { + a = V.isUndefined(matrix.a) ? 1 : matrix.a; + d = V.isUndefined(matrix.d) ? 1 : matrix.d; + b = matrix.b; + c = matrix.c; + } else { + a = d = 1; + } + return { + sx: b ? sqrt(a * a + b * b) : a, + sy: c ? sqrt(c * c + d * d) : d + }; + }, + + // Return the `rotate` transformation from the following equation: + // `translate(tx, ty) . rotate(angle) . scale(sx, sy) === matrix(a,b,c,d,e,f)` + V.matrixToRotate = function(matrix) { + + var p = { x: 0, y: 1 }; + if (matrix) { + p = V.deltaTransformPoint(matrix, p); + } + + return { + angle: g.normalizeAngle(g.toDeg(atan2(p.y, p.x)) - 90) + }; + }, + + // Return the `translate` transformation from the following equation: + // `translate(tx, ty) . rotate(angle) . scale(sx, sy) === matrix(a,b,c,d,e,f)` + V.matrixToTranslate = function(matrix) { + + return { + tx: (matrix && matrix.e) || 0, + ty: (matrix && matrix.f) || 0 + }; + }, + + V.isV = function(object) { + + return object instanceof V; + }; + + // For backwards compatibility: + V.isVElement = V.isV; + + var svgDocument = V('svg').node; + + V.createSVGMatrix = function(matrix) { + + var svgMatrix = svgDocument.createSVGMatrix(); + for (var component in matrix) { + svgMatrix[component] = matrix[component]; + } + + return svgMatrix; + }; + + V.createSVGTransform = function(matrix) { + + if (!V.isUndefined(matrix)) { + + if (!(matrix instanceof SVGMatrix)) { + matrix = V.createSVGMatrix(matrix); + } + + return svgDocument.createSVGTransformFromMatrix(matrix); + } + + return svgDocument.createSVGTransform(); + }; + + V.createSVGPoint = function(x, y) { + + var p = svgDocument.createSVGPoint(); + p.x = x; + p.y = y; + return p; + }; + + V.transformRect = function(r, matrix) { + + var p = svgDocument.createSVGPoint(); + + p.x = r.x; + p.y = r.y; + var corner1 = p.matrixTransform(matrix); + + p.x = r.x + r.width; + p.y = r.y; + var corner2 = p.matrixTransform(matrix); + + p.x = r.x + r.width; + p.y = r.y + r.height; + var corner3 = p.matrixTransform(matrix); + + p.x = r.x; + p.y = r.y + r.height; + var corner4 = p.matrixTransform(matrix); + + var minX = min(corner1.x, corner2.x, corner3.x, corner4.x); + var maxX = max(corner1.x, corner2.x, corner3.x, corner4.x); + var minY = min(corner1.y, corner2.y, corner3.y, corner4.y); + var maxY = max(corner1.y, corner2.y, corner3.y, corner4.y); + + return new g.Rect(minX, minY, maxX - minX, maxY - minY); + }; + + V.transformPoint = function(p, matrix) { + + return new g.Point(V.createSVGPoint(p.x, p.y).matrixTransform(matrix)); + }; + + V.transformLine = function(l, matrix) { + + return new g.Line( + V.transformPoint(l.start, matrix), + V.transformPoint(l.end, matrix) + ); + }; + + V.transformPolyline = function(p, matrix) { + + var inPoints = (p instanceof g.Polyline) ? p.points : p; + if (!V.isArray(inPoints)) inPoints = []; + var outPoints = []; + for (var i = 0, n = inPoints.length; i < n; i++) outPoints[i] = V.transformPoint(inPoints[i], matrix); + return new g.Polyline(outPoints); + }, + + // Convert a style represented as string (e.g. `'fill="blue"; stroke="red"'`) to + // an object (`{ fill: 'blue', stroke: 'red' }`). + V.styleToObject = function(styleString) { + var ret = {}; + var styles = styleString.split(';'); + for (var i = 0; i < styles.length; i++) { + var style = styles[i]; + var pair = style.split('='); + ret[pair[0].trim()] = pair[1].trim(); + } + return ret; + }; + + // Inspired by d3.js https://github.com/mbostock/d3/blob/master/src/svg/arc.js + V.createSlicePathData = function(innerRadius, outerRadius, startAngle, endAngle) { + + var svgArcMax = 2 * PI - 1e-6; + var r0 = innerRadius; + var r1 = outerRadius; + var a0 = startAngle; + var a1 = endAngle; + var da = (a1 < a0 && (da = a0, a0 = a1, a1 = da), a1 - a0); + var df = da < PI ? '0' : '1'; + var c0 = cos(a0); + var s0 = sin(a0); + var c1 = cos(a1); + var s1 = sin(a1); + + return (da >= svgArcMax) + ? (r0 + ? 'M0,' + r1 + + 'A' + r1 + ',' + r1 + ' 0 1,1 0,' + (-r1) + + 'A' + r1 + ',' + r1 + ' 0 1,1 0,' + r1 + + 'M0,' + r0 + + 'A' + r0 + ',' + r0 + ' 0 1,0 0,' + (-r0) + + 'A' + r0 + ',' + r0 + ' 0 1,0 0,' + r0 + + 'Z' + : 'M0,' + r1 + + 'A' + r1 + ',' + r1 + ' 0 1,1 0,' + (-r1) + + 'A' + r1 + ',' + r1 + ' 0 1,1 0,' + r1 + + 'Z') + : (r0 + ? 'M' + r1 * c0 + ',' + r1 * s0 + + 'A' + r1 + ',' + r1 + ' 0 ' + df + ',1 ' + r1 * c1 + ',' + r1 * s1 + + 'L' + r0 * c1 + ',' + r0 * s1 + + 'A' + r0 + ',' + r0 + ' 0 ' + df + ',0 ' + r0 * c0 + ',' + r0 * s0 + + 'Z' + : 'M' + r1 * c0 + ',' + r1 * s0 + + 'A' + r1 + ',' + r1 + ' 0 ' + df + ',1 ' + r1 * c1 + ',' + r1 * s1 + + 'L0,0' + + 'Z'); + }; + + // Merge attributes from object `b` with attributes in object `a`. + // Note that this modifies the object `a`. + // Also important to note that attributes are merged but CSS classes are concatenated. + V.mergeAttrs = function(a, b) { + + for (var attr in b) { + + if (attr === 'class') { + // Concatenate classes. + a[attr] = a[attr] ? a[attr] + ' ' + b[attr] : b[attr]; + } else if (attr === 'style') { + // `style` attribute can be an object. + if (V.isObject(a[attr]) && V.isObject(b[attr])) { + // `style` stored in `a` is an object. + a[attr] = V.mergeAttrs(a[attr], b[attr]); + } else if (V.isObject(a[attr])) { + // `style` in `a` is an object but it's a string in `b`. + // Convert the style represented as a string to an object in `b`. + a[attr] = V.mergeAttrs(a[attr], V.styleToObject(b[attr])); + } else if (V.isObject(b[attr])) { + // `style` in `a` is a string, in `b` it's an object. + a[attr] = V.mergeAttrs(V.styleToObject(a[attr]), b[attr]); + } else { + // Both styles are strings. + a[attr] = V.mergeAttrs(V.styleToObject(a[attr]), V.styleToObject(b[attr])); + } + } else { + a[attr] = b[attr]; + } + } + + return a; + }; + + V.annotateString = function(t, annotations, opt) { + + annotations = annotations || []; + opt = opt || {}; + + var offset = opt.offset || 0; + var compacted = []; + var batch; + var ret = []; + var item; + var prev; + + for (var i = 0; i < t.length; i++) { + + item = ret[i] = t[i]; + + for (var j = 0; j < annotations.length; j++) { + + var annotation = annotations[j]; + var start = annotation.start + offset; + var end = annotation.end + offset; + + if (i >= start && i < end) { + // Annotation applies. + if (V.isObject(item)) { + // There is more than one annotation to be applied => Merge attributes. + item.attrs = V.mergeAttrs(V.mergeAttrs({}, item.attrs), annotation.attrs); + } else { + item = ret[i] = { t: t[i], attrs: annotation.attrs }; + } + if (opt.includeAnnotationIndices) { + (item.annotations || (item.annotations = [])).push(j); + } + } + } + + prev = ret[i - 1]; + + if (!prev) { + + batch = item; + + } else if (V.isObject(item) && V.isObject(prev)) { + // Both previous item and the current one are annotations. If the attributes + // didn't change, merge the text. + if (JSON.stringify(item.attrs) === JSON.stringify(prev.attrs)) { + batch.t += item.t; + } else { + compacted.push(batch); + batch = item; + } + + } else if (V.isObject(item)) { + // Previous item was a string, current item is an annotation. + compacted.push(batch); + batch = item; + + } else if (V.isObject(prev)) { + // Previous item was an annotation, current item is a string. + compacted.push(batch); + batch = item; + + } else { + // Both previous and current item are strings. + batch = (batch || '') + item; + } + } + + if (batch) { + compacted.push(batch); + } + + return compacted; + }; + + V.findAnnotationsAtIndex = function(annotations, index) { + + var found = []; + + if (annotations) { + + annotations.forEach(function(annotation) { + + if (annotation.start < index && index <= annotation.end) { + found.push(annotation); + } + }); + } + + return found; + }; + + V.findAnnotationsBetweenIndexes = function(annotations, start, end) { + + var found = []; + + if (annotations) { + + annotations.forEach(function(annotation) { + + if ((start >= annotation.start && start < annotation.end) || (end > annotation.start && end <= annotation.end) || (annotation.start >= start && annotation.end < end)) { + found.push(annotation); + } + }); + } + + return found; + }; + + // Shift all the text annotations after character `index` by `offset` positions. + V.shiftAnnotations = function(annotations, index, offset) { + + if (annotations) { + + annotations.forEach(function(annotation) { + + if (annotation.start < index && annotation.end >= index) { + annotation.end += offset; + } else if (annotation.start >= index) { + annotation.start += offset; + annotation.end += offset; + } + }); + } + + return annotations; + }; + + V.convertLineToPathData = function(line) { + + line = V(line); + var d = [ + 'M', line.attr('x1'), line.attr('y1'), + 'L', line.attr('x2'), line.attr('y2') + ].join(' '); + return d; + }; + + V.convertPolygonToPathData = function(polygon) { + + var points = V.getPointsFromSvgNode(polygon); + if (points.length === 0) return null; + + return V.svgPointsToPath(points) + ' Z'; + }; + + V.convertPolylineToPathData = function(polyline) { + + var points = V.getPointsFromSvgNode(polyline); + if (points.length === 0) return null; + + return V.svgPointsToPath(points); + }; + + V.svgPointsToPath = function(points) { + + for (var i = 0, n = points.length; i < n; i++) { + points[i] = points[i].x + ' ' + points[i].y; + } + + return 'M ' + points.join(' L'); + }; + + V.getPointsFromSvgNode = function(node) { + + node = V.toNode(node); + var points = []; + var nodePoints = node.points; + if (nodePoints) { + for (var i = 0, n = nodePoints.numberOfItems; i < n; i++) { + points.push(nodePoints.getItem(i)); + } + } + + return points; + }; + + V.KAPPA = 0.551784; + + V.convertCircleToPathData = function(circle) { + + circle = V(circle); + var cx = parseFloat(circle.attr('cx')) || 0; + var cy = parseFloat(circle.attr('cy')) || 0; + var r = parseFloat(circle.attr('r')); + var cd = r * V.KAPPA; // Control distance. + + var d = [ + 'M', cx, cy - r, // Move to the first point. + 'C', cx + cd, cy - r, cx + r, cy - cd, cx + r, cy, // I. Quadrant. + 'C', cx + r, cy + cd, cx + cd, cy + r, cx, cy + r, // II. Quadrant. + 'C', cx - cd, cy + r, cx - r, cy + cd, cx - r, cy, // III. Quadrant. + 'C', cx - r, cy - cd, cx - cd, cy - r, cx, cy - r, // IV. Quadrant. + 'Z' + ].join(' '); + return d; + }; + + V.convertEllipseToPathData = function(ellipse) { + + ellipse = V(ellipse); + var cx = parseFloat(ellipse.attr('cx')) || 0; + var cy = parseFloat(ellipse.attr('cy')) || 0; + var rx = parseFloat(ellipse.attr('rx')); + var ry = parseFloat(ellipse.attr('ry')) || rx; + var cdx = rx * V.KAPPA; // Control distance x. + var cdy = ry * V.KAPPA; // Control distance y. + + var d = [ + 'M', cx, cy - ry, // Move to the first point. + 'C', cx + cdx, cy - ry, cx + rx, cy - cdy, cx + rx, cy, // I. Quadrant. + 'C', cx + rx, cy + cdy, cx + cdx, cy + ry, cx, cy + ry, // II. Quadrant. + 'C', cx - cdx, cy + ry, cx - rx, cy + cdy, cx - rx, cy, // III. Quadrant. + 'C', cx - rx, cy - cdy, cx - cdx, cy - ry, cx, cy - ry, // IV. Quadrant. + 'Z' + ].join(' '); + return d; + }; + + V.convertRectToPathData = function(rect) { + + rect = V(rect); + + return V.rectToPath({ + x: parseFloat(rect.attr('x')) || 0, + y: parseFloat(rect.attr('y')) || 0, + width: parseFloat(rect.attr('width')) || 0, + height: parseFloat(rect.attr('height')) || 0, + rx: parseFloat(rect.attr('rx')) || 0, + ry: parseFloat(rect.attr('ry')) || 0 + }); + }; + + // Convert a rectangle to SVG path commands. `r` is an object of the form: + // `{ x: [number], y: [number], width: [number], height: [number], top-ry: [number], top-ry: [number], bottom-rx: [number], bottom-ry: [number] }`, + // where `x, y, width, height` are the usual rectangle attributes and [top-/bottom-]rx/ry allows for + // specifying radius of the rectangle for all its sides (as opposed to the built-in SVG rectangle + // that has only `rx` and `ry` attributes). + V.rectToPath = function(r) { + + var d; + var x = r.x; + var y = r.y; + var width = r.width; + var height = r.height; + var topRx = min(r.rx || r['top-rx'] || 0, width / 2); + var bottomRx = min(r.rx || r['bottom-rx'] || 0, width / 2); + var topRy = min(r.ry || r['top-ry'] || 0, height / 2); + var bottomRy = min(r.ry || r['bottom-ry'] || 0, height / 2); + + if (topRx || bottomRx || topRy || bottomRy) { + d = [ + 'M', x, y + topRy, + 'v', height - topRy - bottomRy, + 'a', bottomRx, bottomRy, 0, 0, 0, bottomRx, bottomRy, + 'h', width - 2 * bottomRx, + 'a', bottomRx, bottomRy, 0, 0, 0, bottomRx, -bottomRy, + 'v', -(height - bottomRy - topRy), + 'a', topRx, topRy, 0, 0, 0, -topRx, -topRy, + 'h', -(width - 2 * topRx), + 'a', topRx, topRy, 0, 0, 0, -topRx, topRy, + 'Z' + ]; + } else { + d = [ + 'M', x, y, + 'H', x + width, + 'V', y + height, + 'H', x, + 'V', y, + 'Z' + ]; + } + + return d.join(' '); + }; + + // Take a path data string + // Return a normalized path data string + // If data cannot be parsed, return 'M 0 0' + // Adapted from Rappid normalizePath polyfill + // Highly inspired by Raphael Library (www.raphael.com) + V.normalizePathData = (function() { + + var spaces = '\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029'; + var pathCommand = new RegExp('([a-z])[' + spaces + ',]*((-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?[' + spaces + ']*,?[' + spaces + ']*)+)', 'ig'); + var pathValues = new RegExp('(-?\\d*\\.?\\d*(?:e[\\-+]?\\d+)?)[' + spaces + ']*,?[' + spaces + ']*', 'ig'); + + var math = Math; + var PI = math.PI; + var sin = math.sin; + var cos = math.cos; + var tan = math.tan; + var asin = math.asin; + var sqrt = math.sqrt; + var abs = math.abs; + + function q2c(x1, y1, ax, ay, x2, y2) { + + var _13 = 1 / 3; + var _23 = 2 / 3; + return [(_13 * x1) + (_23 * ax), (_13 * y1) + (_23 * ay), (_13 * x2) + (_23 * ax), (_13 * y2) + (_23 * ay), x2, y2]; + } + + function a2c(x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) { + // for more information of where this math came from visit: + // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes + + var _120 = (PI * 120) / 180; + var rad = (PI / 180) * (+angle || 0); + var res = []; + var xy; + + var rotate = function(x, y, rad) { + + var X = (x * cos(rad)) - (y * sin(rad)); + var Y = (x * sin(rad)) + (y * cos(rad)); + return { x: X, y: Y }; + }; + + if (!recursive) { + xy = rotate(x1, y1, -rad); + x1 = xy.x; + y1 = xy.y; + + xy = rotate(x2, y2, -rad); + x2 = xy.x; + y2 = xy.y; + + var x = (x1 - x2) / 2; + var y = (y1 - y2) / 2; + var h = ((x * x) / (rx * rx)) + ((y * y) / (ry * ry)); + + if (h > 1) { + h = sqrt(h); + rx = h * rx; + ry = h * ry; + } + + var rx2 = rx * rx; + var ry2 = ry * ry; + + var k = ((large_arc_flag == sweep_flag) ? -1 : 1) * sqrt(abs(((rx2 * ry2) - (rx2 * y * y) - (ry2 * x * x)) / ((rx2 * y * y) + (ry2 * x * x)))); + + var cx = ((k * rx * y) / ry) + ((x1 + x2) / 2); + var cy = ((k * -ry * x) / rx) + ((y1 + y2) / 2); + + var f1 = asin(((y1 - cy) / ry).toFixed(9)); + var f2 = asin(((y2 - cy) / ry).toFixed(9)); + + f1 = ((x1 < cx) ? (PI - f1) : f1); + f2 = ((x2 < cx) ? (PI - f2) : f2); + + if (f1 < 0) f1 = (PI * 2) + f1; + if (f2 < 0) f2 = (PI * 2) + f2; + + if ((sweep_flag && f1) > f2) f1 = f1 - (PI * 2); + if ((!sweep_flag && f2) > f1) f2 = f2 - (PI * 2); + + } else { + f1 = recursive[0]; + f2 = recursive[1]; + cx = recursive[2]; + cy = recursive[3]; + } + + var df = f2 - f1; + + if (abs(df) > _120) { + var f2old = f2; + var x2old = x2; + var y2old = y2; + + f2 = f1 + (_120 * (((sweep_flag && f2) > f1) ? 1 : -1)); + x2 = cx + (rx * cos(f2)); + y2 = cy + (ry * sin(f2)); + + res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]); + } + + df = f2 - f1; + + var c1 = cos(f1); + var s1 = sin(f1); + var c2 = cos(f2); + var s2 = sin(f2); + + var t = tan(df / 4); + + var hx = (4 / 3) * (rx * t); + var hy = (4 / 3) * (ry * t); + + var m1 = [x1, y1]; + var m2 = [x1 + (hx * s1), y1 - (hy * c1)]; + var m3 = [x2 + (hx * s2), y2 - (hy * c2)]; + var m4 = [x2, y2]; + + m2[0] = (2 * m1[0]) - m2[0]; + m2[1] = (2 * m1[1]) - m2[1]; + + if (recursive) { + return [m2, m3, m4].concat(res); + + } else { + res = [m2, m3, m4].concat(res).join().split(','); + + var newres = []; + var ii = res.length; + for (var i = 0; i < ii; i++) { + newres[i] = (i % 2) ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x; + } + + return newres; + } + } + + function parsePathString(pathString) { + + if (!pathString) return null; + + var paramCounts = { a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0 }; + var data = []; + + String(pathString).replace(pathCommand, function(a, b, c) { + + var params = []; + var name = b.toLowerCase(); + c.replace(pathValues, function(a, b) { + if (b) params.push(+b); + }); + + if ((name === 'm') && (params.length > 2)) { + data.push([b].concat(params.splice(0, 2))); + name = 'l'; + b = ((b === 'm') ? 'l' : 'L'); + } + + while (params.length >= paramCounts[name]) { + data.push([b].concat(params.splice(0, paramCounts[name]))); + if (!paramCounts[name]) break; + } + }); + + return data; + } + + function pathToAbsolute(pathArray) { + + if (!Array.isArray(pathArray) || !Array.isArray(pathArray && pathArray[0])) { // rough assumption + pathArray = parsePathString(pathArray); + } + + // if invalid string, return 'M 0 0' + if (!pathArray || !pathArray.length) return [['M', 0, 0]]; + + var res = []; + var x = 0; + var y = 0; + var mx = 0; + var my = 0; + var start = 0; + var pa0; + + var ii = pathArray.length; + for (var i = start; i < ii; i++) { + + var r = []; + res.push(r); + + var pa = pathArray[i]; + pa0 = pa[0]; + + if (pa0 != pa0.toUpperCase()) { + r[0] = pa0.toUpperCase(); + + var jj; + var j; + switch (r[0]) { + case 'A': + r[1] = pa[1]; + r[2] = pa[2]; + r[3] = pa[3]; + r[4] = pa[4]; + r[5] = pa[5]; + r[6] = +pa[6] + x; + r[7] = +pa[7] + y; + break; + + case 'V': + r[1] = +pa[1] + y; + break; + + case 'H': + r[1] = +pa[1] + x; + break; + + case 'M': + mx = +pa[1] + x; + my = +pa[2] + y; + + jj = pa.length; + for (j = 1; j < jj; j++) { + r[j] = +pa[j] + ((j % 2) ? x : y); + } + break; + + default: + jj = pa.length; + for (j = 1; j < jj; j++) { + r[j] = +pa[j] + ((j % 2) ? x : y); + } + break; + } + } else { + var kk = pa.length; + for (var k = 0; k < kk; k++) { + r[k] = pa[k]; + } + } + + switch (r[0]) { + case 'Z': + x = +mx; + y = +my; + break; + + case 'H': + x = r[1]; + break; + + case 'V': + y = r[1]; + break; + + case 'M': + mx = r[r.length - 2]; + my = r[r.length - 1]; + x = r[r.length - 2]; + y = r[r.length - 1]; + break; + + default: + x = r[r.length - 2]; + y = r[r.length - 1]; + break; + } + } + + return res; + } + + function normalize(path) { + + var p = pathToAbsolute(path); + var attrs = { x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null }; + + function processPath(path, d, pcom) { + + var nx, ny; + + if (!path) return ['C', d.x, d.y, d.x, d.y, d.x, d.y]; + + if (!(path[0] in { T: 1, Q: 1 })) { + d.qx = null; + d.qy = null; + } + + switch (path[0]) { + case 'M': + d.X = path[1]; + d.Y = path[2]; + break; + + case 'A': + path = ['C'].concat(a2c.apply(0, [d.x, d.y].concat(path.slice(1)))); + break; + + case 'S': + if (pcom === 'C' || pcom === 'S') { // In 'S' case we have to take into account, if the previous command is C/S. + nx = (d.x * 2) - d.bx; // And reflect the previous + ny = (d.y * 2) - d.by; // command's control point relative to the current point. + } + else { // or some else or nothing + nx = d.x; + ny = d.y; + } + path = ['C', nx, ny].concat(path.slice(1)); + break; + + case 'T': + if (pcom === 'Q' || pcom === 'T') { // In 'T' case we have to take into account, if the previous command is Q/T. + d.qx = (d.x * 2) - d.qx; // And make a reflection similar + d.qy = (d.y * 2) - d.qy; // to case 'S'. + } + else { // or something else or nothing + d.qx = d.x; + d.qy = d.y; + } + path = ['C'].concat(q2c(d.x, d.y, d.qx, d.qy, path[1], path[2])); + break; + + case 'Q': + d.qx = path[1]; + d.qy = path[2]; + path = ['C'].concat(q2c(d.x, d.y, path[1], path[2], path[3], path[4])); + break; + + case 'H': + path = ['L'].concat(path[1], d.y); + break; + + case 'V': + path = ['L'].concat(d.x, path[1]); + break; + + // leave 'L' & 'Z' commands as they were: + + case 'L': + break; + + case 'Z': + break; + } + + return path; + } + + function fixArc(pp, i) { + + if (pp[i].length > 7) { + + pp[i].shift(); + var pi = pp[i]; + + while (pi.length) { + pcoms[i] = 'A'; // if created multiple 'C's, their original seg is saved + pp.splice(i++, 0, ['C'].concat(pi.splice(0, 6))); + } + + pp.splice(i, 1); + ii = p.length; + } + } + + var pcoms = []; // path commands of original path p + var pfirst = ''; // temporary holder for original path command + var pcom = ''; // holder for previous path command of original path + + var ii = p.length; + for (var i = 0; i < ii; i++) { + if (p[i]) pfirst = p[i][0]; // save current path command + + if (pfirst !== 'C') { // C is not saved yet, because it may be result of conversion + pcoms[i] = pfirst; // Save current path command + if (i > 0) pcom = pcoms[i - 1]; // Get previous path command pcom + } + + p[i] = processPath(p[i], attrs, pcom); // Previous path command is inputted to processPath + + if (pcoms[i] !== 'A' && pfirst === 'C') pcoms[i] = 'C'; // 'A' is the only command + // which may produce multiple 'C's + // so we have to make sure that 'C' is also 'C' in original path + + fixArc(p, i); // fixArc adds also the right amount of 'A's to pcoms + + var seg = p[i]; + var seglen = seg.length; + + attrs.x = seg[seglen - 2]; + attrs.y = seg[seglen - 1]; + + attrs.bx = parseFloat(seg[seglen - 4]) || attrs.x; + attrs.by = parseFloat(seg[seglen - 3]) || attrs.y; + } + + // make sure normalized path data string starts with an M segment + if (!p[0][0] || p[0][0] !== 'M') { + p.unshift(['M', 0, 0]); + } + + return p; + } + + return function(pathData) { + return normalize(pathData).join(',').split(',').join(' '); + }; + })(); + + V.namespace = ns; + + return V; + +})(); + +// Global namespace. + +var joint = { + + version: '2.1.2', + + config: { + // The class name prefix config is for advanced use only. + // Be aware that if you change the prefix, the JointJS CSS will no longer function properly. + classNamePrefix: 'joint-', + defaultTheme: 'default' + }, + + // `joint.dia` namespace. + dia: {}, + + // `joint.ui` namespace. + ui: {}, + + // `joint.layout` namespace. + layout: {}, + + // `joint.shapes` namespace. + shapes: {}, + + // `joint.format` namespace. + format: {}, + + // `joint.connectors` namespace. + connectors: {}, + + // `joint.highlighters` namespace. + highlighters: {}, + + // `joint.routers` namespace. + routers: {}, + + // `joint.anchors` namespace. + anchors: {}, + + // `joint.connectionPoints` namespace. + connectionPoints: {}, + + // `joint.connectionStrategies` namespace. + connectionStrategies: {}, + + // `joint.linkTools` namespace. + linkTools: {}, + + // `joint.mvc` namespace. + mvc: { + views: {} + }, + + setTheme: function(theme, opt) { + + opt = opt || {}; + + joint.util.invoke(joint.mvc.views, 'setTheme', theme, opt); + + // Update the default theme on the view prototype. + joint.mvc.View.prototype.defaultTheme = theme; + }, + + // `joint.env` namespace. + env: { + + _results: {}, + + _tests: { + + svgforeignobject: function() { + return !!document.createElementNS && + /SVGForeignObject/.test(({}).toString.call(document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject'))); + } + }, + + addTest: function(name, fn) { + + return joint.env._tests[name] = fn; + }, + + test: function(name) { + + var fn = joint.env._tests[name]; + + if (!fn) { + throw new Error('Test not defined ("' + name + '"). Use `joint.env.addTest(name, fn) to add a new test.`'); + } + + var result = joint.env._results[name]; + + if (typeof result !== 'undefined') { + return result; + } + + try { + result = fn(); + } catch (error) { + result = false; + } + + // Cache the test result. + joint.env._results[name] = result; + + return result; + } + }, + + util: { + + // Return a simple hash code from a string. See http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/. + hashCode: function(str) { + + var hash = 0; + if (str.length == 0) return hash; + for (var i = 0; i < str.length; i++) { + var c = str.charCodeAt(i); + hash = ((hash << 5) - hash) + c; + hash = hash & hash; // Convert to 32bit integer + } + return hash; + }, + + getByPath: function(obj, path, delim) { + + var keys = Array.isArray(path) ? path.slice() : path.split(delim || '/'); + var key; + + while (keys.length) { + key = keys.shift(); + if (Object(obj) === obj && key in obj) { + obj = obj[key]; + } else { + return undefined; + } + } + return obj; + }, + + setByPath: function(obj, path, value, delim) { + + var keys = Array.isArray(path) ? path : path.split(delim || '/'); + + var diver = obj; + var i = 0; + + for (var len = keys.length; i < len - 1; i++) { + // diver creates an empty object if there is no nested object under such a key. + // This means that one can populate an empty nested object with setByPath(). + diver = diver[keys[i]] || (diver[keys[i]] = {}); + } + diver[keys[len - 1]] = value; + + return obj; + }, + + unsetByPath: function(obj, path, delim) { + + delim = delim || '/'; + + var pathArray = Array.isArray(path) ? path.slice() : path.split(delim); + + var propertyToRemove = pathArray.pop(); + if (pathArray.length > 0) { + + // unsetting a nested attribute + var parent = joint.util.getByPath(obj, pathArray, delim); + + if (parent) { + delete parent[propertyToRemove]; + } + + } else { + + // unsetting a primitive attribute + delete obj[propertyToRemove]; + } + + return obj; + }, + + flattenObject: function(obj, delim, stop) { + + delim = delim || '/'; + var ret = {}; + + for (var key in obj) { + + if (!obj.hasOwnProperty(key)) continue; + + var shouldGoDeeper = typeof obj[key] === 'object'; + if (shouldGoDeeper && stop && stop(obj[key])) { + shouldGoDeeper = false; + } + + if (shouldGoDeeper) { + + var flatObject = this.flattenObject(obj[key], delim, stop); + + for (var flatKey in flatObject) { + if (!flatObject.hasOwnProperty(flatKey)) continue; + ret[key + delim + flatKey] = flatObject[flatKey]; + } + + } else { + + ret[key] = obj[key]; + } + } + + return ret; + }, + + uuid: function() { + + // credit: http://stackoverflow.com/posts/2117523/revisions + + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + var r = Math.random() * 16|0; + var v = c == 'x' ? r : (r&0x3|0x8); + return v.toString(16); + }); + }, + + // Generate global unique id for obj and store it as a property of the object. + guid: function(obj) { + + this.guid.id = this.guid.id || 1; + obj.id = (obj.id === undefined ? 'j_' + this.guid.id++ : obj.id); + return obj.id; + }, + + toKebabCase: function(string) { + + return string.replace(/[A-Z]/g, '-$&').toLowerCase(); + }, + + // Copy all the properties to the first argument from the following arguments. + // All the properties will be overwritten by the properties from the following + // arguments. Inherited properties are ignored. + mixin: _.assign, + + // Copy all properties to the first argument from the following + // arguments only in case if they don't exists in the first argument. + // All the function propererties in the first argument will get + // additional property base pointing to the extenders same named + // property function's call method. + supplement: _.defaults, + + // Same as `mixin()` but deep version. + deepMixin: _.mixin, + + // Same as `supplement()` but deep version. + deepSupplement: _.defaultsDeep, + + normalizeEvent: function(evt) { + + var touchEvt = evt.originalEvent && evt.originalEvent.changedTouches && evt.originalEvent.changedTouches[0]; + if (touchEvt) { + for (var property in evt) { + // copy all the properties from the input event that are not + // defined on the touch event (functions included). + if (touchEvt[property] === undefined) { + touchEvt[property] = evt[property]; + } + } + return touchEvt; + } + + return evt; + }, + + nextFrame: (function() { + + var raf; + + if (typeof window !== 'undefined') { + + raf = window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame; + } + + if (!raf) { + + var lastTime = 0; + + raf = function(callback) { + + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = setTimeout(function() { callback(currTime + timeToCall); }, timeToCall); + + lastTime = currTime + timeToCall; + + return id; + }; + } + + return function(callback, context) { + return context + ? raf(callback.bind(context)) + : raf(callback); + }; + + })(), + + cancelFrame: (function() { + + var caf; + var client = typeof window != 'undefined'; + + if (client) { + + caf = window.cancelAnimationFrame || + window.webkitCancelAnimationFrame || + window.webkitCancelRequestAnimationFrame || + window.msCancelAnimationFrame || + window.msCancelRequestAnimationFrame || + window.oCancelAnimationFrame || + window.oCancelRequestAnimationFrame || + window.mozCancelAnimationFrame || + window.mozCancelRequestAnimationFrame; + } + + caf = caf || clearTimeout; + + return client ? caf.bind(window) : caf; + + })(), + + // ** Deprecated ** + shapePerimeterConnectionPoint: function(linkView, view, magnet, reference) { + + var bbox; + var spot; + + if (!magnet) { + + // There is no magnet, try to make the best guess what is the + // wrapping SVG element. This is because we want this "smart" + // connection points to work out of the box without the + // programmer to put magnet marks to any of the subelements. + // For example, we want the functoin to work on basic.Path elements + // without any special treatment of such elements. + // The code below guesses the wrapping element based on + // one simple assumption. The wrapping elemnet is the + // first child of the scalable group if such a group exists + // or the first child of the rotatable group if not. + // This makese sense because usually the wrapping element + // is below any other sub element in the shapes. + var scalable = view.$('.scalable')[0]; + var rotatable = view.$('.rotatable')[0]; + + if (scalable && scalable.firstChild) { + + magnet = scalable.firstChild; + + } else if (rotatable && rotatable.firstChild) { + + magnet = rotatable.firstChild; + } + } + + if (magnet) { + + spot = V(magnet).findIntersection(reference, linkView.paper.viewport); + if (!spot) { + bbox = V(magnet).getBBox({ target: linkView.paper.viewport }); + } + + } else { + + bbox = view.model.getBBox(); + spot = bbox.intersectionWithLineFromCenterToPoint(reference); + } + return spot || bbox.center(); + }, + + isPercentage: function(val) { + + return joint.util.isString(val) && val.slice(-1) === '%'; + }, + + parseCssNumeric: function(strValue, restrictUnits) { + + restrictUnits = restrictUnits || []; + var cssNumeric = { value: parseFloat(strValue) }; + + if (Number.isNaN(cssNumeric.value)) { + return null; + } + + var validUnitsExp = restrictUnits.join('|'); + + if (joint.util.isString(strValue)) { + var matches = new RegExp('(\\d+)(' + validUnitsExp + ')$').exec(strValue); + if (!matches) { + return null; + } + if (matches[2]) { + cssNumeric.unit = matches[2]; + } + } + return cssNumeric; + }, + + breakText: function(text, size, styles, opt) { + + opt = opt || {}; + styles = styles || {}; + + var width = size.width; + var height = size.height; + + var svgDocument = opt.svgDocument || V('svg').node; + var textSpan = V('tspan').node; + var textElement = V('text').attr(styles).append(textSpan).node; + var textNode = document.createTextNode(''); + + // Prevent flickering + textElement.style.opacity = 0; + // Prevent FF from throwing an uncaught exception when `getBBox()` + // called on element that is not in the render tree (is not measurable). + // .getComputedTextLength() returns always 0 in this case. + // Note that the `textElement` resp. `textSpan` can become hidden + // when it's appended to the DOM and a `display: none` CSS stylesheet + // rule gets applied. + textElement.style.display = 'block'; + textSpan.style.display = 'block'; + + textSpan.appendChild(textNode); + svgDocument.appendChild(textElement); + + if (!opt.svgDocument) { + + document.body.appendChild(svgDocument); + } + + var separator = opt.separator || ' '; + var eol = opt.eol || '\n'; + + var words = text.split(separator); + var full = []; + var lines = []; + var p; + var lineHeight; + + for (var i = 0, l = 0, len = words.length; i < len; i++) { + + var word = words[i]; + + if (!word) continue; + + if (eol && word.indexOf(eol) >= 0) { + // word cotains end-of-line character + if (word.length > 1) { + // separate word and continue cycle + var eolWords = word.split(eol); + for (var j = 0, jl = eolWords.length - 1; j < jl; j++) { + eolWords.splice(2 * j + 1, 0, eol); + } + Array.prototype.splice.apply(words, [i, 1].concat(eolWords)); + i--; + len += eolWords.length - 1; + } else { + // creates new line + l++; + } + continue; + } + + + textNode.data = lines[l] ? lines[l] + ' ' + word : word; + + if (textSpan.getComputedTextLength() <= width) { + + // the current line fits + lines[l] = textNode.data; + + if (p) { + // We were partitioning. Put rest of the word onto next line + full[l++] = true; + + // cancel partitioning + p = 0; + } + + } else { + + if (!lines[l] || p) { + + var partition = !!p; + + p = word.length - 1; + + if (partition || !p) { + + // word has only one character. + if (!p) { + + if (!lines[l]) { + + // we won't fit this text within our rect + lines = []; + + break; + } + + // partitioning didn't help on the non-empty line + // try again, but this time start with a new line + + // cancel partitions created + words.splice(i, 2, word + words[i + 1]); + + // adjust word length + len--; + + full[l++] = true; + i--; + + continue; + } + + // move last letter to the beginning of the next word + words[i] = word.substring(0, p); + words[i + 1] = word.substring(p) + words[i + 1]; + + } else { + + // We initiate partitioning + // split the long word into two words + words.splice(i, 1, word.substring(0, p), word.substring(p)); + + // adjust words length + len++; + + if (l && !full[l - 1]) { + // if the previous line is not full, try to fit max part of + // the current word there + l--; + } + } + + i--; + + continue; + } + + l++; + i--; + } + + // if size.height is defined we have to check whether the height of the entire + // text exceeds the rect height + if (height !== undefined) { + + if (lineHeight === undefined) { + + var heightValue; + + // use the same defaults as in V.prototype.text + if (styles.lineHeight === 'auto') { + heightValue = { value: 1.5, unit: 'em' }; + } else { + heightValue = joint.util.parseCssNumeric(styles.lineHeight, ['em']) || { value: 1, unit: 'em' }; + } + + lineHeight = heightValue.value; + if (heightValue.unit === 'em' ) { + lineHeight *= textElement.getBBox().height; + } + } + + if (lineHeight * lines.length > height) { + + // remove overflowing lines + lines.splice(Math.floor(height / lineHeight)); + + break; + } + } + } + + if (opt.svgDocument) { + + // svg document was provided, remove the text element only + svgDocument.removeChild(textElement); + + } else { + + // clean svg document + document.body.removeChild(svgDocument); + } + + return lines.join(eol); + }, + + // Sanitize HTML + // Based on https://gist.github.com/ufologist/5a0da51b2b9ef1b861c30254172ac3c9 + // Parses a string into an array of DOM nodes. + // Then outputs it back as a string. + sanitizeHTML: function(html) { + + // Ignores tags that are invalid inside a
tag (e.g. , ) + + // If documentContext (second parameter) is not specified or given as `null` or `undefined`, a new document is used. + // Inline events will not execute when the HTML is parsed; this includes, for example, sending GET requests for images. + + // If keepScripts (last parameter) is `false`, scripts are not executed. + var output = $($.parseHTML('
' + html + '
', null, false)); + + output.find('*').each(function() { // for all nodes + var currentNode = this; + + $.each(currentNode.attributes, function() { // for all attributes in each node + var currentAttribute = this; + + var attrName = currentAttribute.name; + var attrValue = currentAttribute.value; + + // Remove attribute names that start with "on" (e.g. onload, onerror...). + // Remove attribute values that start with "javascript:" pseudo protocol (e.g. `href="javascript:alert(1)"`). + if (attrName.indexOf('on') === 0 || attrValue.indexOf('javascript:') === 0) { + $(currentNode).removeAttr(attrName); + } + }); + }); + + return output.html(); + }, + + // Download `blob` as file with `fileName`. + // Does not work in IE9. + downloadBlob: function(blob, fileName) { + + if (window.navigator.msSaveBlob) { // requires IE 10+ + // pulls up a save dialog + window.navigator.msSaveBlob(blob, fileName); + + } else { // other browsers + // downloads directly in Chrome and Safari + + // presents a save/open dialog in Firefox + // Firefox bug: `from` field in save dialog always shows `from:blob:` + // https://bugzilla.mozilla.org/show_bug.cgi?id=1053327 + + var url = window.URL.createObjectURL(blob); + var link = document.createElement('a'); + + link.href = url; + link.download = fileName; + document.body.appendChild(link); + + link.click(); + + document.body.removeChild(link); + window.URL.revokeObjectURL(url); // mark the url for garbage collection + } + }, + + // Download `dataUri` as file with `fileName`. + // Does not work in IE9. + downloadDataUri: function(dataUri, fileName) { + + var blob = joint.util.dataUriToBlob(dataUri); + joint.util.downloadBlob(blob, fileName); + }, + + // Convert an uri-encoded data component (possibly also base64-encoded) to a blob. + dataUriToBlob: function(dataUri) { + + // first, make sure there are no newlines in the data uri + dataUri = dataUri.replace(/\s/g, ''); + dataUri = decodeURIComponent(dataUri); + + var firstCommaIndex = dataUri.indexOf(','); // split dataUri as `dataTypeString`,`data` + + var dataTypeString = dataUri.slice(0, firstCommaIndex); // e.g. 'data:image/jpeg;base64' + var mimeString = dataTypeString.split(':')[1].split(';')[0]; // e.g. 'image/jpeg' + + var data = dataUri.slice(firstCommaIndex + 1); + var decodedString; + if (dataTypeString.indexOf('base64') >= 0) { // data may be encoded in base64 + decodedString = atob(data); // decode data + } else { + // convert the decoded string to UTF-8 + decodedString = unescape(encodeURIComponent(data)); + } + // write the bytes of the string to a typed array + var ia = new window.Uint8Array(decodedString.length); + for (var i = 0; i < decodedString.length; i++) { + ia[i] = decodedString.charCodeAt(i); + } + + return new Blob([ia], { type: mimeString }); // return the typed array as Blob + }, + + // Read an image at `url` and return it as base64-encoded data uri. + // The mime type of the image is inferred from the `url` file extension. + // If data uri is provided as `url`, it is returned back unchanged. + // `callback` is a method with `err` as first argument and `dataUri` as second argument. + // Works with IE9. + imageToDataUri: function(url, callback) { + + if (!url || url.substr(0, 'data:'.length) === 'data:') { + // No need to convert to data uri if it is already in data uri. + + // This not only convenient but desired. For example, + // IE throws a security error if data:image/svg+xml is used to render + // an image to the canvas and an attempt is made to read out data uri. + // Now if our image is already in data uri, there is no need to render it to the canvas + // and so we can bypass this error. + + // Keep the async nature of the function. + return setTimeout(function() { + callback(null, url); + }, 0); + } + + // chrome, IE10+ + var modernHandler = function(xhr, callback) { + + if (xhr.status === 200) { + + var reader = new FileReader(); + + reader.onload = function(evt) { + var dataUri = evt.target.result; + callback(null, dataUri); + }; + + reader.onerror = function() { + callback(new Error('Failed to load image ' + url)); + }; + + reader.readAsDataURL(xhr.response); + } else { + callback(new Error('Failed to load image ' + url)); + } + }; + + var legacyHandler = function(xhr, callback) { + + var Uint8ToString = function(u8a) { + var CHUNK_SZ = 0x8000; + var c = []; + for (var i = 0; i < u8a.length; i += CHUNK_SZ) { + c.push(String.fromCharCode.apply(null, u8a.subarray(i, i + CHUNK_SZ))); + } + return c.join(''); + }; + + if (xhr.status === 200) { + + var bytes = new Uint8Array(xhr.response); + + var suffix = (url.split('.').pop()) || 'png'; + var map = { + 'svg': 'svg+xml' + }; + var meta = 'data:image/' + (map[suffix] || suffix) + ';base64,'; + var b64encoded = meta + btoa(Uint8ToString(bytes)); + callback(null, b64encoded); + } else { + callback(new Error('Failed to load image ' + url)); + } + }; + + var xhr = new XMLHttpRequest(); + + xhr.open('GET', url, true); + xhr.addEventListener('error', function() { + callback(new Error('Failed to load image ' + url)); + }); + + xhr.responseType = window.FileReader ? 'blob' : 'arraybuffer'; + + xhr.addEventListener('load', function() { + if (window.FileReader) { + modernHandler(xhr, callback); + } else { + legacyHandler(xhr, callback); + } + }); + + xhr.send(); + }, + + getElementBBox: function(el) { + + var $el = $(el); + if ($el.length === 0) { + throw new Error('Element not found') + } + + var element = $el[0]; + var doc = element.ownerDocument; + var clientBBox = element.getBoundingClientRect(); + + var strokeWidthX = 0; + var strokeWidthY = 0; + + // Firefox correction + if (element.ownerSVGElement) { + + var vel = V(element); + var bbox = vel.getBBox({ target: vel.svg() }); + + // if FF getBoundingClientRect includes stroke-width, getBBox doesn't. + // To unify this across all browsers we need to adjust the final bBox with `stroke-width` value. + strokeWidthX = (clientBBox.width - bbox.width); + strokeWidthY = (clientBBox.height - bbox.height); + } + + return { + x: clientBBox.left + window.pageXOffset - doc.documentElement.offsetLeft + strokeWidthX / 2, + y: clientBBox.top + window.pageYOffset - doc.documentElement.offsetTop + strokeWidthY / 2, + width: clientBBox.width - strokeWidthX, + height: clientBBox.height - strokeWidthY + }; + }, + + + // Highly inspired by the jquery.sortElements plugin by Padolsey. + // See http://james.padolsey.com/javascript/sorting-elements-with-jquery/. + sortElements: function(elements, comparator) { + + var $elements = $(elements); + var placements = $elements.map(function() { + + var sortElement = this; + var parentNode = sortElement.parentNode; + // Since the element itself will change position, we have + // to have some way of storing it's original position in + // the DOM. The easiest way is to have a 'flag' node: + var nextSibling = parentNode.insertBefore(document.createTextNode(''), sortElement.nextSibling); + + return function() { + + if (parentNode === this) { + throw new Error('You can\'t sort elements if any one is a descendant of another.'); + } + + // Insert before flag: + parentNode.insertBefore(this, nextSibling); + // Remove flag: + parentNode.removeChild(nextSibling); + }; + }); + + return Array.prototype.sort.call($elements, comparator).each(function(i) { + placements[i].call(this); + }); + }, + + // Sets attributes on the given element and its descendants based on the selector. + // `attrs` object: { [SELECTOR1]: { attrs1 }, [SELECTOR2]: { attrs2}, ... } e.g. { 'input': { color : 'red' }} + setAttributesBySelector: function(element, attrs) { + + var $element = $(element); + + joint.util.forIn(attrs, function(attrs, selector) { + var $elements = $element.find(selector).addBack().filter(selector); + // Make a special case for setting classes. + // We do not want to overwrite any existing class. + if (joint.util.has(attrs, 'class')) { + $elements.addClass(attrs['class']); + attrs = joint.util.omit(attrs, 'class'); + } + $elements.attr(attrs); + }); + }, + + // Return a new object with all four sides (top, right, bottom, left) in it. + // Value of each side is taken from the given argument (either number or object). + // Default value for a side is 0. + // Examples: + // joint.util.normalizeSides(5) --> { top: 5, right: 5, bottom: 5, left: 5 } + // joint.util.normalizeSides({ horizontal: 5 }) --> { top: 0, right: 5, bottom: 0, left: 5 } + // joint.util.normalizeSides({ left: 5 }) --> { top: 0, right: 0, bottom: 0, left: 5 } + // joint.util.normalizeSides({ horizontal: 10, left: 5 }) --> { top: 0, right: 10, bottom: 0, left: 5 } + // joint.util.normalizeSides({ horizontal: 0, left: 5 }) --> { top: 0, right: 0, bottom: 0, left: 5 } + normalizeSides: function(box) { + + if (Object(box) !== box) { // `box` is not an object + var val = 0; // `val` left as 0 if `box` cannot be understood as finite number + if (isFinite(box)) val = +box; // actually also accepts string numbers (e.g. '100') + + return { top: val, right: val, bottom: val, left: val }; + } + + // `box` is an object + var top, right, bottom, left; + top = right = bottom = left = 0; + + if (isFinite(box.vertical)) top = bottom = +box.vertical; + if (isFinite(box.horizontal)) right = left = +box.horizontal; + + if (isFinite(box.top)) top = +box.top; // overwrite vertical + if (isFinite(box.right)) right = +box.right; // overwrite horizontal + if (isFinite(box.bottom)) bottom = +box.bottom; // overwrite vertical + if (isFinite(box.left)) left = +box.left; // overwrite horizontal + + return { top: top, right: right, bottom: bottom, left: left }; + }, + + timing: { + + linear: function(t) { + return t; + }, + + quad: function(t) { + return t * t; + }, + + cubic: function(t) { + return t * t * t; + }, + + inout: function(t) { + if (t <= 0) return 0; + if (t >= 1) return 1; + var t2 = t * t; + var t3 = t2 * t; + return 4 * (t < .5 ? t3 : 3 * (t - t2) + t3 - .75); + }, + + exponential: function(t) { + return Math.pow(2, 10 * (t - 1)); + }, + + bounce: function(t) { + for (var a = 0, b = 1; 1; a += b, b /= 2) { + if (t >= (7 - 4 * a) / 11) { + var q = (11 - 6 * a - 11 * t) / 4; + return -q * q + b * b; + } + } + }, + + reverse: function(f) { + return function(t) { + return 1 - f(1 - t); + }; + }, + + reflect: function(f) { + return function(t) { + return .5 * (t < .5 ? f(2 * t) : (2 - f(2 - 2 * t))); + }; + }, + + clamp: function(f, n, x) { + n = n || 0; + x = x || 1; + return function(t) { + var r = f(t); + return r < n ? n : r > x ? x : r; + }; + }, + + back: function(s) { + if (!s) s = 1.70158; + return function(t) { + return t * t * ((s + 1) * t - s); + }; + }, + + elastic: function(x) { + if (!x) x = 1.5; + return function(t) { + return Math.pow(2, 10 * (t - 1)) * Math.cos(20 * Math.PI * x / 3 * t); + }; + } + }, + + interpolate: { + + number: function(a, b) { + var d = b - a; + return function(t) { return a + d * t; }; + }, + + object: function(a, b) { + var s = Object.keys(a); + return function(t) { + var i, p; + var r = {}; + for (i = s.length - 1; i != -1; i--) { + p = s[i]; + r[p] = a[p] + (b[p] - a[p]) * t; + } + return r; + }; + }, + + hexColor: function(a, b) { + + var ca = parseInt(a.slice(1), 16); + var cb = parseInt(b.slice(1), 16); + var ra = ca & 0x0000ff; + var rd = (cb & 0x0000ff) - ra; + var ga = ca & 0x00ff00; + var gd = (cb & 0x00ff00) - ga; + var ba = ca & 0xff0000; + var bd = (cb & 0xff0000) - ba; + + return function(t) { + + var r = (ra + rd * t) & 0x000000ff; + var g = (ga + gd * t) & 0x0000ff00; + var b = (ba + bd * t) & 0x00ff0000; + + return '#' + (1 << 24 | r | g | b ).toString(16).slice(1); + }; + }, + + unit: function(a, b) { + + var r = /(-?[0-9]*.[0-9]*)(px|em|cm|mm|in|pt|pc|%)/; + var ma = r.exec(a); + var mb = r.exec(b); + var p = mb[1].indexOf('.'); + var f = p > 0 ? mb[1].length - p - 1 : 0; + a = +ma[1]; + var d = +mb[1] - a; + var u = ma[2]; + + return function(t) { + return (a + d * t).toFixed(f) + u; + }; + } + }, + + // SVG filters. + filter: { + + // `color` ... outline color + // `width`... outline width + // `opacity` ... outline opacity + // `margin` ... gap between outline and the element + outline: function(args) { + + var tpl = ''; + + var margin = Number.isFinite(args.margin) ? args.margin : 2; + var width = Number.isFinite(args.width) ? args.width : 1; + + return joint.util.template(tpl)({ + color: args.color || 'blue', + opacity: Number.isFinite(args.opacity) ? args.opacity : 1, + outerRadius: margin + width, + innerRadius: margin + }); + }, + + // `color` ... color + // `width`... width + // `blur` ... blur + // `opacity` ... opacity + highlight: function(args) { + + var tpl = ''; + + return joint.util.template(tpl)({ + color: args.color || 'red', + width: Number.isFinite(args.width) ? args.width : 1, + blur: Number.isFinite(args.blur) ? args.blur : 0, + opacity: Number.isFinite(args.opacity) ? args.opacity : 1 + }); + }, + + // `x` ... horizontal blur + // `y` ... vertical blur (optional) + blur: function(args) { + + var x = Number.isFinite(args.x) ? args.x : 2; + + return joint.util.template('')({ + stdDeviation: Number.isFinite(args.y) ? [x, args.y] : x + }); + }, + + // `dx` ... horizontal shift + // `dy` ... vertical shift + // `blur` ... blur + // `color` ... color + // `opacity` ... opacity + dropShadow: function(args) { + + var tpl = 'SVGFEDropShadowElement' in window + ? '' + : ''; + + return joint.util.template(tpl)({ + dx: args.dx || 0, + dy: args.dy || 0, + opacity: Number.isFinite(args.opacity) ? args.opacity : 1, + color: args.color || 'black', + blur: Number.isFinite(args.blur) ? args.blur : 4 + }); + }, + + // `amount` ... the proportion of the conversion. A value of 1 is completely grayscale. A value of 0 leaves the input unchanged. + grayscale: function(args) { + + var amount = Number.isFinite(args.amount) ? args.amount : 1; + + return joint.util.template('')({ + a: 0.2126 + 0.7874 * (1 - amount), + b: 0.7152 - 0.7152 * (1 - amount), + c: 0.0722 - 0.0722 * (1 - amount), + d: 0.2126 - 0.2126 * (1 - amount), + e: 0.7152 + 0.2848 * (1 - amount), + f: 0.0722 - 0.0722 * (1 - amount), + g: 0.2126 - 0.2126 * (1 - amount), + h: 0.0722 + 0.9278 * (1 - amount) + }); + }, + + // `amount` ... the proportion of the conversion. A value of 1 is completely sepia. A value of 0 leaves the input unchanged. + sepia: function(args) { + + var amount = Number.isFinite(args.amount) ? args.amount : 1; + + return joint.util.template('')({ + a: 0.393 + 0.607 * (1 - amount), + b: 0.769 - 0.769 * (1 - amount), + c: 0.189 - 0.189 * (1 - amount), + d: 0.349 - 0.349 * (1 - amount), + e: 0.686 + 0.314 * (1 - amount), + f: 0.168 - 0.168 * (1 - amount), + g: 0.272 - 0.272 * (1 - amount), + h: 0.534 - 0.534 * (1 - amount), + i: 0.131 + 0.869 * (1 - amount) + }); + }, + + // `amount` ... the proportion of the conversion. A value of 0 is completely un-saturated. A value of 1 leaves the input unchanged. + saturate: function(args) { + + var amount = Number.isFinite(args.amount) ? args.amount : 1; + + return joint.util.template('')({ + amount: 1 - amount + }); + }, + + // `angle` ... the number of degrees around the color circle the input samples will be adjusted. + hueRotate: function(args) { + + return joint.util.template('')({ + angle: args.angle || 0 + }); + }, + + // `amount` ... the proportion of the conversion. A value of 1 is completely inverted. A value of 0 leaves the input unchanged. + invert: function(args) { + + var amount = Number.isFinite(args.amount) ? args.amount : 1; + + return joint.util.template('')({ + amount: amount, + amount2: 1 - amount + }); + }, + + // `amount` ... proportion of the conversion. A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. + brightness: function(args) { + + return joint.util.template('')({ + amount: Number.isFinite(args.amount) ? args.amount : 1 + }); + }, + + // `amount` ... proportion of the conversion. A value of 0 will create an image that is completely black. A value of 1 leaves the input unchanged. + contrast: function(args) { + + var amount = Number.isFinite(args.amount) ? args.amount : 1; + + return joint.util.template('')({ + amount: amount, + amount2: .5 - amount / 2 + }); + } + }, + + format: { + + // Formatting numbers via the Python Format Specification Mini-language. + // See http://docs.python.org/release/3.1.3/library/string.html#format-specification-mini-language. + // Heavilly inspired by the D3.js library implementation. + number: function(specifier, value, locale) { + + locale = locale || { + + currency: ['$', ''], + decimal: '.', + thousands: ',', + grouping: [3] + }; + + // See Python format specification mini-language: http://docs.python.org/release/3.1.3/library/string.html#format-specification-mini-language. + // [[fill]align][sign][symbol][0][width][,][.precision][type] + var re = /(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i; + + var match = re.exec(specifier); + var fill = match[1] || ' '; + var align = match[2] || '>'; + var sign = match[3] || ''; + var symbol = match[4] || ''; + var zfill = match[5]; + var width = +match[6]; + var comma = match[7]; + var precision = match[8]; + var type = match[9]; + var scale = 1; + var prefix = ''; + var suffix = ''; + var integer = false; + + if (precision) precision = +precision.substring(1); + + if (zfill || fill === '0' && align === '=') { + zfill = fill = '0'; + align = '='; + if (comma) width -= Math.floor((width - 1) / 4); + } + + switch (type) { + case 'n': + comma = true; type = 'g'; + break; + case '%': + scale = 100; suffix = '%'; type = 'f'; + break; + case 'p': + scale = 100; suffix = '%'; type = 'r'; + break; + case 'b': + case 'o': + case 'x': + case 'X': + if (symbol === '#') prefix = '0' + type.toLowerCase(); + break; + case 'c': + case 'd': + integer = true; precision = 0; + break; + case 's': + scale = -1; type = 'r'; + break; + } + + if (symbol === '$') { + prefix = locale.currency[0]; + suffix = locale.currency[1]; + } + + // If no precision is specified for `'r'`, fallback to general notation. + if (type == 'r' && !precision) type = 'g'; + + // Ensure that the requested precision is in the supported range. + if (precision != null) { + if (type == 'g') precision = Math.max(1, Math.min(21, precision)); + else if (type == 'e' || type == 'f') precision = Math.max(0, Math.min(20, precision)); + } + + var zcomma = zfill && comma; + + // Return the empty string for floats formatted as ints. + if (integer && (value % 1)) return ''; + + // Convert negative to positive, and record the sign prefix. + var negative = value < 0 || value === 0 && 1 / value < 0 ? (value = -value, '-') : sign; + + var fullSuffix = suffix; + + // Apply the scale, computing it from the value's exponent for si format. + // Preserve the existing suffix, if any, such as the currency symbol. + if (scale < 0) { + var unit = this.prefix(value, precision); + value = unit.scale(value); + fullSuffix = unit.symbol + suffix; + } else { + value *= scale; + } + + // Convert to the desired precision. + value = this.convert(type, value, precision); + + // Break the value into the integer part (before) and decimal part (after). + var i = value.lastIndexOf('.'); + var before = i < 0 ? value : value.substring(0, i); + var after = i < 0 ? '' : locale.decimal + value.substring(i + 1); + + function formatGroup(value) { + + var i = value.length; + var t = []; + var j = 0; + var g = locale.grouping[0]; + while (i > 0 && g > 0) { + t.push(value.substring(i -= g, i + g)); + g = locale.grouping[j = (j + 1) % locale.grouping.length]; + } + return t.reverse().join(locale.thousands); + } + + // If the fill character is not `'0'`, grouping is applied before padding. + if (!zfill && comma && locale.grouping) { + + before = formatGroup(before); + } + + var length = prefix.length + before.length + after.length + (zcomma ? 0 : negative.length); + var padding = length < width ? new Array(length = width - length + 1).join(fill) : ''; + + // If the fill character is `'0'`, grouping is applied after padding. + if (zcomma) before = formatGroup(padding + before); + + // Apply prefix. + negative += prefix; + + // Rejoin integer and decimal parts. + value = before + after; + + return (align === '<' ? negative + value + padding + : align === '>' ? padding + negative + value + : align === '^' ? padding.substring(0, length >>= 1) + negative + value + padding.substring(length) + : negative + (zcomma ? value : padding + value)) + fullSuffix; + }, + + // Formatting string via the Python Format string. + // See https://docs.python.org/2/library/string.html#format-string-syntax) + string: function(formatString, value) { + + var fieldDelimiterIndex; + var fieldDelimiter = '{'; + var endPlaceholder = false; + var formattedStringArray = []; + + while ((fieldDelimiterIndex = formatString.indexOf(fieldDelimiter)) !== -1) { + + var pieceFormatedString, formatSpec, fieldName; + + pieceFormatedString = formatString.slice(0, fieldDelimiterIndex); + + if (endPlaceholder) { + formatSpec = pieceFormatedString.split(':'); + fieldName = formatSpec.shift().split('.'); + pieceFormatedString = value; + + for (var i = 0; i < fieldName.length; i++) + pieceFormatedString = pieceFormatedString[fieldName[i]]; + + if (formatSpec.length) + pieceFormatedString = this.number(formatSpec, pieceFormatedString); + } + + formattedStringArray.push(pieceFormatedString); + + formatString = formatString.slice(fieldDelimiterIndex + 1); + fieldDelimiter = (endPlaceholder = !endPlaceholder) ? '}' : '{'; + } + formattedStringArray.push(formatString); + + return formattedStringArray.join(''); + }, + + convert: function(type, value, precision) { + + switch (type) { + case 'b': return value.toString(2); + case 'c': return String.fromCharCode(value); + case 'o': return value.toString(8); + case 'x': return value.toString(16); + case 'X': return value.toString(16).toUpperCase(); + case 'g': return value.toPrecision(precision); + case 'e': return value.toExponential(precision); + case 'f': return value.toFixed(precision); + case 'r': return (value = this.round(value, this.precision(value, precision))).toFixed(Math.max(0, Math.min(20, this.precision(value * (1 + 1e-15), precision)))); + default: return value + ''; + } + }, + + round: function(value, precision) { + + return precision + ? Math.round(value * (precision = Math.pow(10, precision))) / precision + : Math.round(value); + }, + + precision: function(value, precision) { + + return precision - (value ? Math.ceil(Math.log(value) / Math.LN10) : 1); + }, + + prefix: function(value, precision) { + + var prefixes = ['y', 'z', 'a', 'f', 'p', 'n', 'µ', 'm', '', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'].map(function(d, i) { + var k = Math.pow(10, Math.abs(8 - i) * 3); + return { + scale: i > 8 ? function(d) { return d / k; } : function(d) { return d * k; }, + symbol: d + }; + }); + + var i = 0; + if (value) { + if (value < 0) value *= -1; + if (precision) value = this.round(value, this.precision(value, precision)); + i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10); + i = Math.max(-24, Math.min(24, Math.floor((i <= 0 ? i + 1 : i - 1) / 3) * 3)); + } + return prefixes[8 + i / 3]; + } + }, + + /* + Pre-compile the HTML to be used as a template. + */ + template: function(html) { + + /* + Must support the variation in templating syntax found here: + https://lodash.com/docs#template + */ + var regex = /<%= ([^ ]+) %>|\$\{ ?([^\{\} ]+) ?\}|\{\{([^\{\} ]+)\}\}/g; + + return function(data) { + + data = data || {}; + + return html.replace(regex, function(match) { + + var args = Array.from(arguments); + var attr = args.slice(1, 4).find(function(_attr) { + return !!_attr; + }); + + var attrArray = attr.split('.'); + var value = data[attrArray.shift()]; + + while (value !== undefined && attrArray.length) { + value = value[attrArray.shift()]; + } + + return value !== undefined ? value : ''; + }); + }; + }, + + /** + * @param {Element=} el Element, which content is intent to display in full-screen mode, 'window.top.document.body' is default. + */ + toggleFullScreen: function(el) { + + var topDocument = window.top.document; + el = el || topDocument.body; + + function prefixedResult(el, prop) { + + var prefixes = ['webkit', 'moz', 'ms', 'o', '']; + for (var i = 0; i < prefixes.length; i++) { + var prefix = prefixes[i]; + var propName = prefix ? (prefix + prop) : (prop.substr(0, 1).toLowerCase() + prop.substr(1)); + if (el[propName] !== undefined) { + return joint.util.isFunction(el[propName]) ? el[propName]() : el[propName]; + } + } + } + + if (prefixedResult(topDocument, 'FullscreenElement') || prefixedResult(topDocument, 'FullScreenElement')) { + prefixedResult(topDocument, 'ExitFullscreen') || // Spec. + prefixedResult(topDocument, 'CancelFullScreen'); // Firefox + } else { + prefixedResult(el, 'RequestFullscreen') || // Spec. + prefixedResult(el, 'RequestFullScreen'); // Firefox + } + }, + + addClassNamePrefix: function(className) { + + if (!className) return className; + + return className.toString().split(' ').map(function(_className) { + + if (_className.substr(0, joint.config.classNamePrefix.length) !== joint.config.classNamePrefix) { + _className = joint.config.classNamePrefix + _className; + } + + return _className; + + }).join(' '); + }, + + removeClassNamePrefix: function(className) { + + if (!className) return className; + + return className.toString().split(' ').map(function(_className) { + + if (_className.substr(0, joint.config.classNamePrefix.length) === joint.config.classNamePrefix) { + _className = _className.substr(joint.config.classNamePrefix.length); + } + + return _className; + + }).join(' '); + }, + + wrapWith: function(object, methods, wrapper) { + + if (joint.util.isString(wrapper)) { + + if (!joint.util.wrappers[wrapper]) { + throw new Error('Unknown wrapper: "' + wrapper + '"'); + } + + wrapper = joint.util.wrappers[wrapper]; + } + + if (!joint.util.isFunction(wrapper)) { + throw new Error('Wrapper must be a function.'); + } + + this.toArray(methods).forEach(function(method) { + object[method] = wrapper(object[method]); + }); + }, + + wrappers: { + + /* + Prepares a function with the following usage: + + fn([cell, cell, cell], opt); + fn([cell, cell, cell]); + fn(cell, cell, cell, opt); + fn(cell, cell, cell); + fn(cell); + */ + cells: function(fn) { + + return function() { + + var args = Array.from(arguments); + var n = args.length; + var cells = n > 0 && args[0] || []; + var opt = n > 1 && args[n - 1] || {}; + + if (!Array.isArray(cells)) { + + if (opt instanceof joint.dia.Cell) { + cells = args; + } else if (cells instanceof joint.dia.Cell) { + if (args.length > 1) { + args.pop(); + } + cells = args; + } + } + + if (opt instanceof joint.dia.Cell) { + opt = {}; + } + + return fn.call(this, cells, opt); + }; + } + }, + + parseDOMJSON: function(json, namespace) { + + var selectors = {}; + var svgNamespace = V.namespace.xmlns; + var ns = namespace || svgNamespace; + var fragment = document.createDocumentFragment(); + var queue = [json, fragment, ns]; + while (queue.length > 0) { + ns = queue.pop(); + var parentNode = queue.pop(); + var siblingsDef = queue.pop(); + for (var i = 0, n = siblingsDef.length; i < n; i++) { + var nodeDef = siblingsDef[i]; + // TagName + if (!nodeDef.hasOwnProperty('tagName')) throw new Error('json-dom-parser: missing tagName'); + var tagName = nodeDef.tagName; + // Namespace URI + if (nodeDef.hasOwnProperty('namespaceURI')) ns = nodeDef.namespaceURI; + var node = document.createElementNS(ns, tagName); + var svg = (ns === svgNamespace); + var wrapper = (svg) ? V : $; + // Attributes + var attributes = nodeDef.attributes; + if (attributes) wrapper(node).attr(attributes); + // Style + var style = nodeDef.style; + if (style) $(node).css(style); + // ClassName + if (nodeDef.hasOwnProperty('className')) { + var className = nodeDef.className; + if (svg) { + node.className.baseVal = className; + } else { + node.className = className; + } + } + // Selector + if (nodeDef.hasOwnProperty('selector')) { + var nodeSelector = nodeDef.selector; + if (selectors[nodeSelector]) throw new Error('json-dom-parser: selector must be unique'); + selectors[nodeSelector] = node; + wrapper(node).attr('joint-selector', nodeSelector); + } + parentNode.appendChild(node); + // Children + var childrenDef = nodeDef.children; + if (Array.isArray(childrenDef)) queue.push(childrenDef, node, ns); + } + } + return { + fragment: fragment, + selectors: selectors + } + }, + + // lodash 3 vs 4 incompatible + sortedIndex: _.sortedIndexBy || _.sortedIndex, + uniq: _.uniqBy || _.uniq, + uniqueId: _.uniqueId, + sortBy: _.sortBy, + isFunction: _.isFunction, + result: _.result, + union: _.union, + invoke: _.invokeMap || _.invoke, + difference: _.difference, + intersection: _.intersection, + omit: _.omit, + pick: _.pick, + has: _.has, + bindAll: _.bindAll, + assign: _.assign, + defaults: _.defaults, + defaultsDeep: _.defaultsDeep, + isPlainObject: _.isPlainObject, + isEmpty: _.isEmpty, + isEqual: _.isEqual, + noop: function() {}, + cloneDeep: _.cloneDeep, + toArray: _.toArray, + flattenDeep: _.flattenDeep, + camelCase: _.camelCase, + groupBy: _.groupBy, + forIn: _.forIn, + without: _.without, + debounce: _.debounce, + clone: _.clone, + + isBoolean: function(value) { + var toString = Object.prototype.toString; + return value === true || value === false || (!!value && typeof value === 'object' && toString.call(value) === '[object Boolean]'); + }, + + isObject: function(value) { + return !!value && (typeof value === 'object' || typeof value === 'function'); + }, + + isNumber: function(value) { + var toString = Object.prototype.toString; + return typeof value === 'number' || (!!value && typeof value === 'object' && toString.call(value) === '[object Number]'); + }, + + isString: function(value) { + var toString = Object.prototype.toString; + return typeof value === 'string' || (!!value && typeof value === 'object' && toString.call(value) === '[object String]'); + }, + + merge: function() { + if (_.mergeWith) { + var args = Array.from(arguments); + var last = args[args.length - 1]; + + var customizer = this.isFunction(last) ? last : this.noop; + args.push(function(a,b) { + var customResult = customizer(a, b); + if (customResult !== undefined) { + return customResult; + } + + if (Array.isArray(a) && !Array.isArray(b)) { + return b; + } + }); + + return _.mergeWith.apply(this, args) + } + return _.merge.apply(this, arguments); + } + } +}; + + +joint.mvc.View = Backbone.View.extend({ + + options: {}, + theme: null, + themeClassNamePrefix: joint.util.addClassNamePrefix('theme-'), + requireSetThemeOverride: false, + defaultTheme: joint.config.defaultTheme, + children: null, + childNodes: null, + + constructor: function(options) { + + this.requireSetThemeOverride = options && !!options.theme; + this.options = joint.util.assign({}, this.options, options); + + Backbone.View.call(this, options); + }, + + initialize: function(options) { + + joint.util.bindAll(this, 'setTheme', 'onSetTheme', 'remove', 'onRemove'); + + joint.mvc.views[this.cid] = this; + + this.setTheme(this.options.theme || this.defaultTheme); + this.init(); + }, + + renderChildren: function(children) { + children || (children = this.children); + if (children) { + var namespace = V.namespace[this.svgElement ? 'xmlns' : 'xhtml']; + var doc = joint.util.parseDOMJSON(children, namespace); + this.vel.empty().append(doc.fragment); + this.childNodes = doc.selectors; + } + return this; + }, + + // Override the Backbone `_ensureElement()` method in order to create an + // svg element (e.g., ``) node that wraps all the nodes of the Cell view. + // Expose class name setter as a separate method. + _ensureElement: function() { + if (!this.el) { + var tagName = joint.util.result(this, 'tagName'); + var attrs = joint.util.assign({}, joint.util.result(this, 'attributes')); + if (this.id) attrs.id = joint.util.result(this, 'id'); + this.setElement(this._createElement(tagName)); + this._setAttributes(attrs); + } else { + this.setElement(joint.util.result(this, 'el')); + } + this._ensureElClassName(); + }, + + _setAttributes: function(attrs) { + if (this.svgElement) { + this.vel.attr(attrs); + } else { + this.$el.attr(attrs); + } + }, + + _createElement: function(tagName) { + if (this.svgElement) { + return document.createElementNS(V.namespace.xmlns, tagName); + } else { + return document.createElement(tagName); + } + }, + + // Utilize an alternative DOM manipulation API by + // adding an element reference wrapped in Vectorizer. + _setElement: function(el) { + this.$el = el instanceof Backbone.$ ? el : Backbone.$(el); + this.el = this.$el[0]; + if (this.svgElement) this.vel = V(this.el); + }, + + _ensureElClassName: function() { + var className = joint.util.result(this, 'className'); + var prefixedClassName = joint.util.addClassNamePrefix(className); + // Note: className removal here kept for backwards compatibility only + if (this.svgElement) { + this.vel.removeClass(className).addClass(prefixedClassName); + } else { + this.$el.removeClass(className).addClass(prefixedClassName); + } + }, + + init: function() { + // Intentionally empty. + // This method is meant to be overriden. + }, + + onRender: function() { + // Intentionally empty. + // This method is meant to be overriden. + }, + + setTheme: function(theme, opt) { + + opt = opt || {}; + + // Theme is already set, override is required, and override has not been set. + // Don't set the theme. + if (this.theme && this.requireSetThemeOverride && !opt.override) { + return this; + } + + this.removeThemeClassName(); + this.addThemeClassName(theme); + this.onSetTheme(this.theme/* oldTheme */, theme/* newTheme */); + this.theme = theme; + + return this; + }, + + addThemeClassName: function(theme) { + + theme = theme || this.theme; + + var className = this.themeClassNamePrefix + theme; + + if (this.svgElement) { + this.vel.addClass(className); + } else { + this.$el.addClass(className); + } + + return this; + }, + + removeThemeClassName: function(theme) { + + theme = theme || this.theme; + + var className = this.themeClassNamePrefix + theme; + + if (this.svgElement) { + this.vel.removeClass(className); + } else { + this.$el.removeClass(className); + } + + return this; + }, + + onSetTheme: function(oldTheme, newTheme) { + // Intentionally empty. + // This method is meant to be overriden. + }, + + remove: function() { + + this.onRemove(); + this.undelegateDocumentEvents(); + + joint.mvc.views[this.cid] = null; + + Backbone.View.prototype.remove.apply(this, arguments); + + return this; + }, + + onRemove: function() { + // Intentionally empty. + // This method is meant to be overriden. + }, + + getEventNamespace: function() { + // Returns a per-session unique namespace + return '.joint-event-ns-' + this.cid; + }, + + delegateElementEvents: function(element, events, data) { + if (!events) return this; + data || (data = {}); + var eventNS = this.getEventNamespace(); + for (var eventName in events) { + var method = events[eventName]; + if (typeof method !== 'function') method = this[method]; + if (!method) continue; + $(element).on(eventName + eventNS, data, method.bind(this)); + } + return this; + }, + + undelegateElementEvents: function(element) { + $(element).off(this.getEventNamespace()); + return this; + }, + + delegateDocumentEvents: function(events, data) { + events || (events = joint.util.result(this, 'documentEvents')); + return this.delegateElementEvents(document, events, data); + }, + + undelegateDocumentEvents: function() { + return this.undelegateElementEvents(document); + }, + + eventData: function(evt, data) { + if (!evt) throw new Error('eventData(): event object required.'); + var currentData = evt.data; + var key = '__' + this.cid + '__'; + if (data === undefined) { + if (!currentData) return {}; + return currentData[key] || {}; + } + currentData || (currentData = evt.data = {}); + currentData[key] || (currentData[key] = {}); + joint.util.assign(currentData[key], data); + return this; + } + +}, { + + extend: function() { + + var args = Array.from(arguments); + + // Deep clone the prototype and static properties objects. + // This prevents unexpected behavior where some properties are overwritten outside of this function. + var protoProps = args[0] && joint.util.assign({}, args[0]) || {}; + var staticProps = args[1] && joint.util.assign({}, args[1]) || {}; + + // Need the real render method so that we can wrap it and call it later. + var renderFn = protoProps.render || (this.prototype && this.prototype.render) || null; + + /* + Wrap the real render method so that: + .. `onRender` is always called. + .. `this` is always returned. + */ + protoProps.render = function() { + + if (renderFn) { + // Call the original render method. + renderFn.apply(this, arguments); + } + + // Should always call onRender() method. + this.onRender(); + + // Should always return itself. + return this; + }; + + return Backbone.View.extend.call(this, protoProps, staticProps); + } +}); + + + +joint.dia.GraphCells = Backbone.Collection.extend({ + + cellNamespace: joint.shapes, + + initialize: function(models, opt) { + + // Set the optional namespace where all model classes are defined. + if (opt.cellNamespace) { + this.cellNamespace = opt.cellNamespace; + } + + this.graph = opt.graph; + }, + + model: function(attrs, opt) { + + var collection = opt.collection; + var namespace = collection.cellNamespace; + + // Find the model class in the namespace or use the default one. + var ModelClass = (attrs.type === 'link') + ? joint.dia.Link + : joint.util.getByPath(namespace, attrs.type, '.') || joint.dia.Element; + + var cell = new ModelClass(attrs, opt); + // Add a reference to the graph. It is necessary to do this here because this is the earliest place + // where a new model is created from a plain JS object. For other objects, see `joint.dia.Graph>>_prepareCell()`. + if (!opt.dry) { + cell.graph = collection.graph; + } + + return cell; + }, + + // `comparator` makes it easy to sort cells based on their `z` index. + comparator: function(model) { + + return model.get('z') || 0; + } +}); + + +joint.dia.Graph = Backbone.Model.extend({ + + _batches: {}, + + initialize: function(attrs, opt) { + + opt = opt || {}; + + // Passing `cellModel` function in the options object to graph allows for + // setting models based on attribute objects. This is especially handy + // when processing JSON graphs that are in a different than JointJS format. + var cells = new joint.dia.GraphCells([], { + model: opt.cellModel, + cellNamespace: opt.cellNamespace, + graph: this + }); + Backbone.Model.prototype.set.call(this, 'cells', cells); + + // Make all the events fired in the `cells` collection available. + // to the outside world. + cells.on('all', this.trigger, this); + + // Backbone automatically doesn't trigger re-sort if models attributes are changed later when + // they're already in the collection. Therefore, we're triggering sort manually here. + this.on('change:z', this._sortOnChangeZ, this); + + // `joint.dia.Graph` keeps an internal data structure (an adjacency list) + // for fast graph queries. All changes that affect the structure of the graph + // must be reflected in the `al` object. This object provides fast answers to + // questions such as "what are the neighbours of this node" or "what + // are the sibling links of this link". + + // Outgoing edges per node. Note that we use a hash-table for the list + // of outgoing edges for a faster lookup. + // [node ID] -> Object [edge] -> true + this._out = {}; + // Ingoing edges per node. + // [node ID] -> Object [edge] -> true + this._in = {}; + // `_nodes` is useful for quick lookup of all the elements in the graph, without + // having to go through the whole cells array. + // [node ID] -> true + this._nodes = {}; + // `_edges` is useful for quick lookup of all the links in the graph, without + // having to go through the whole cells array. + // [edge ID] -> true + this._edges = {}; + + cells.on('add', this._restructureOnAdd, this); + cells.on('remove', this._restructureOnRemove, this); + cells.on('reset', this._restructureOnReset, this); + cells.on('change:source', this._restructureOnChangeSource, this); + cells.on('change:target', this._restructureOnChangeTarget, this); + cells.on('remove', this._removeCell, this); + }, + + _sortOnChangeZ: function() { + + this.get('cells').sort(); + }, + + _restructureOnAdd: function(cell) { + + if (cell.isLink()) { + this._edges[cell.id] = true; + var source = cell.get('source'); + var target = cell.get('target'); + if (source.id) { + (this._out[source.id] || (this._out[source.id] = {}))[cell.id] = true; + } + if (target.id) { + (this._in[target.id] || (this._in[target.id] = {}))[cell.id] = true; + } + } else { + this._nodes[cell.id] = true; + } + }, + + _restructureOnRemove: function(cell) { + + if (cell.isLink()) { + delete this._edges[cell.id]; + var source = cell.get('source'); + var target = cell.get('target'); + if (source.id && this._out[source.id] && this._out[source.id][cell.id]) { + delete this._out[source.id][cell.id]; + } + if (target.id && this._in[target.id] && this._in[target.id][cell.id]) { + delete this._in[target.id][cell.id]; + } + } else { + delete this._nodes[cell.id]; + } + }, + + _restructureOnReset: function(cells) { + + // Normalize into an array of cells. The original `cells` is GraphCells Backbone collection. + cells = cells.models; + + this._out = {}; + this._in = {}; + this._nodes = {}; + this._edges = {}; + + cells.forEach(this._restructureOnAdd, this); + }, + + _restructureOnChangeSource: function(link) { + + var prevSource = link.previous('source'); + if (prevSource.id && this._out[prevSource.id]) { + delete this._out[prevSource.id][link.id]; + } + var source = link.get('source'); + if (source.id) { + (this._out[source.id] || (this._out[source.id] = {}))[link.id] = true; + } + }, + + _restructureOnChangeTarget: function(link) { + + var prevTarget = link.previous('target'); + if (prevTarget.id && this._in[prevTarget.id]) { + delete this._in[prevTarget.id][link.id]; + } + var target = link.get('target'); + if (target.id) { + (this._in[target.id] || (this._in[target.id] = {}))[link.id] = true; + } + }, + + // Return all outbound edges for the node. Return value is an object + // of the form: [edge] -> true + getOutboundEdges: function(node) { + + return (this._out && this._out[node]) || {}; + }, + + // Return all inbound edges for the node. Return value is an object + // of the form: [edge] -> true + getInboundEdges: function(node) { + + return (this._in && this._in[node]) || {}; + }, + + toJSON: function() { + + // Backbone does not recursively call `toJSON()` on attributes that are themselves models/collections. + // It just clones the attributes. Therefore, we must call `toJSON()` on the cells collection explicitely. + var json = Backbone.Model.prototype.toJSON.apply(this, arguments); + json.cells = this.get('cells').toJSON(); + return json; + }, + + fromJSON: function(json, opt) { + + if (!json.cells) { + + throw new Error('Graph JSON must contain cells array.'); + } + + return this.set(json, opt); + }, + + set: function(key, val, opt) { + + var attrs; + + // Handle both `key`, value and {key: value} style arguments. + if (typeof key === 'object') { + attrs = key; + opt = val; + } else { + (attrs = {})[key] = val; + } + + // Make sure that `cells` attribute is handled separately via resetCells(). + if (attrs.hasOwnProperty('cells')) { + this.resetCells(attrs.cells, opt); + attrs = joint.util.omit(attrs, 'cells'); + } + + // The rest of the attributes are applied via original set method. + return Backbone.Model.prototype.set.call(this, attrs, opt); + }, + + clear: function(opt) { + + opt = joint.util.assign({}, opt, { clear: true }); + + var collection = this.get('cells'); + + if (collection.length === 0) return this; + + this.startBatch('clear', opt); + + // The elements come after the links. + var cells = collection.sortBy(function(cell) { + return cell.isLink() ? 1 : 2; + }); + + do { + + // Remove all the cells one by one. + // Note that all the links are removed first, so it's + // safe to remove the elements without removing the connected + // links first. + cells.shift().remove(opt); + + } while (cells.length > 0); + + this.stopBatch('clear'); + + return this; + }, + + _prepareCell: function(cell, opt) { + + var attrs; + if (cell instanceof Backbone.Model) { + attrs = cell.attributes; + if (!cell.graph && (!opt || !opt.dry)) { + // An element can not be member of more than one graph. + // A cell stops being the member of the graph after it's explicitely removed. + cell.graph = this; + } + } else { + // In case we're dealing with a plain JS object, we have to set the reference + // to the `graph` right after the actual model is created. This happens in the `model()` function + // of `joint.dia.GraphCells`. + attrs = cell; + } + + if (!joint.util.isString(attrs.type)) { + throw new TypeError('dia.Graph: cell type must be a string.'); + } + + return cell; + }, + + minZIndex: function() { + + var firstCell = this.get('cells').first(); + return firstCell ? (firstCell.get('z') || 0) : 0; + }, + + maxZIndex: function() { + + var lastCell = this.get('cells').last(); + return lastCell ? (lastCell.get('z') || 0) : 0; + }, + + addCell: function(cell, opt) { + + if (Array.isArray(cell)) { + + return this.addCells(cell, opt); + } + + if (cell instanceof Backbone.Model) { + + if (!cell.has('z')) { + cell.set('z', this.maxZIndex() + 1); + } + + } else if (cell.z === undefined) { + + cell.z = this.maxZIndex() + 1; + } + + this.get('cells').add(this._prepareCell(cell, opt), opt || {}); + + return this; + }, + + addCells: function(cells, opt) { + + if (cells.length) { + + cells = joint.util.flattenDeep(cells); + opt.position = cells.length; + + this.startBatch('add'); + cells.forEach(function(cell) { + opt.position--; + this.addCell(cell, opt); + }, this); + this.stopBatch('add'); + } + + return this; + }, + + // When adding a lot of cells, it is much more efficient to + // reset the entire cells collection in one go. + // Useful for bulk operations and optimizations. + resetCells: function(cells, opt) { + + var preparedCells = joint.util.toArray(cells).map(function(cell) { + return this._prepareCell(cell, opt); + }, this); + this.get('cells').reset(preparedCells, opt); + + return this; + }, + + removeCells: function(cells, opt) { + + if (cells.length) { + + this.startBatch('remove'); + joint.util.invoke(cells, 'remove', opt); + this.stopBatch('remove'); + } + + return this; + }, + + _removeCell: function(cell, collection, options) { + + options = options || {}; + + if (!options.clear) { + // Applications might provide a `disconnectLinks` option set to `true` in order to + // disconnect links when a cell is removed rather then removing them. The default + // is to remove all the associated links. + if (options.disconnectLinks) { + + this.disconnectLinks(cell, options); + + } else { + + this.removeLinks(cell, options); + } + } + // Silently remove the cell from the cells collection. Silently, because + // `joint.dia.Cell.prototype.remove` already triggers the `remove` event which is + // then propagated to the graph model. If we didn't remove the cell silently, two `remove` events + // would be triggered on the graph model. + this.get('cells').remove(cell, { silent: true }); + + if (cell.graph === this) { + // Remove the element graph reference only if the cell is the member of this graph. + cell.graph = null; + } + }, + + // Get a cell by `id`. + getCell: function(id) { + + return this.get('cells').get(id); + }, + + getCells: function() { + + return this.get('cells').toArray(); + }, + + getElements: function() { + return Object.keys(this._nodes).map(this.getCell, this); + }, + + getLinks: function() { + return Object.keys(this._edges).map(this.getCell, this); + }, + + getFirstCell: function() { + + return this.get('cells').first(); + }, + + getLastCell: function() { + + return this.get('cells').last(); + }, + + // Get all inbound and outbound links connected to the cell `model`. + getConnectedLinks: function(model, opt) { + + opt = opt || {}; + + var inbound = opt.inbound; + var outbound = opt.outbound; + if (inbound === undefined && outbound === undefined) { + inbound = outbound = true; + } + + // The final array of connected link models. + var links = []; + // Connected edges. This hash table ([edge] -> true) serves only + // for a quick lookup to check if we already added a link. + var edges = {}; + + if (outbound) { + joint.util.forIn(this.getOutboundEdges(model.id), function(exists, edge) { + if (!edges[edge]) { + links.push(this.getCell(edge)); + edges[edge] = true; + } + }.bind(this)); + } + if (inbound) { + joint.util.forIn(this.getInboundEdges(model.id), function(exists, edge) { + // Skip links that were already added. Those must be self-loop links + // because they are both inbound and outbond edges of the same element. + if (!edges[edge]) { + links.push(this.getCell(edge)); + edges[edge] = true; + } + }.bind(this)); + } + + // If 'deep' option is 'true', return all the links that are connected to any of the descendent cells + // and are not descendents themselves. + if (opt.deep) { + + var embeddedCells = model.getEmbeddedCells({ deep: true }); + // In the first round, we collect all the embedded edges so that we can exclude + // them from the final result. + var embeddedEdges = {}; + embeddedCells.forEach(function(cell) { + if (cell.isLink()) { + embeddedEdges[cell.id] = true; + } + }); + embeddedCells.forEach(function(cell) { + if (cell.isLink()) return; + if (outbound) { + joint.util.forIn(this.getOutboundEdges(cell.id), function(exists, edge) { + if (!edges[edge] && !embeddedEdges[edge]) { + links.push(this.getCell(edge)); + edges[edge] = true; + } + }.bind(this)); + } + if (inbound) { + joint.util.forIn(this.getInboundEdges(cell.id), function(exists, edge) { + if (!edges[edge] && !embeddedEdges[edge]) { + links.push(this.getCell(edge)); + edges[edge] = true; + } + }.bind(this)); + } + }, this); + } + + return links; + }, + + getNeighbors: function(model, opt) { + + opt = opt || {}; + + var inbound = opt.inbound; + var outbound = opt.outbound; + if (inbound === undefined && outbound === undefined) { + inbound = outbound = true; + } + + var neighbors = this.getConnectedLinks(model, opt).reduce(function(res, link) { + + var source = link.get('source'); + var target = link.get('target'); + var loop = link.hasLoop(opt); + + // Discard if it is a point, or if the neighbor was already added. + if (inbound && joint.util.has(source, 'id') && !res[source.id]) { + + var sourceElement = this.getCell(source.id); + + if (loop || (sourceElement && sourceElement !== model && (!opt.deep || !sourceElement.isEmbeddedIn(model)))) { + res[source.id] = sourceElement; + } + } + + // Discard if it is a point, or if the neighbor was already added. + if (outbound && joint.util.has(target, 'id') && !res[target.id]) { + + var targetElement = this.getCell(target.id); + + if (loop || (targetElement && targetElement !== model && (!opt.deep || !targetElement.isEmbeddedIn(model)))) { + res[target.id] = targetElement; + } + } + + return res; + }.bind(this), {}); + + return joint.util.toArray(neighbors); + }, + + getCommonAncestor: function(/* cells */) { + + var cellsAncestors = Array.from(arguments).map(function(cell) { + + var ancestors = []; + var parentId = cell.get('parent'); + + while (parentId) { + + ancestors.push(parentId); + parentId = this.getCell(parentId).get('parent'); + } + + return ancestors; + + }, this); + + cellsAncestors = cellsAncestors.sort(function(a, b) { + return a.length - b.length; + }); + + var commonAncestor = joint.util.toArray(cellsAncestors.shift()).find(function(ancestor) { + return cellsAncestors.every(function(cellAncestors) { + return cellAncestors.includes(ancestor); + }); + }); + + return this.getCell(commonAncestor); + }, + + // Find the whole branch starting at `element`. + // If `opt.deep` is `true`, take into account embedded elements too. + // If `opt.breadthFirst` is `true`, use the Breadth-first search algorithm, otherwise use Depth-first search. + getSuccessors: function(element, opt) { + + opt = opt || {}; + var res = []; + // Modify the options so that it includes the `outbound` neighbors only. In other words, search forwards. + this.search(element, function(el) { + if (el !== element) { + res.push(el); + } + }, joint.util.assign({}, opt, { outbound: true })); + return res; + }, + + // Clone `cells` returning an object that maps the original cell ID to the clone. The number + // of clones is exactly the same as the `cells.length`. + // This function simply clones all the `cells`. However, it also reconstructs + // all the `source/target` and `parent/embed` references within the `cells`. + // This is the main difference from the `cell.clone()` method. The + // `cell.clone()` method works on one single cell only. + // For example, for a graph: `A --- L ---> B`, `cloneCells([A, L, B])` + // returns `[A2, L2, B2]` resulting to a graph: `A2 --- L2 ---> B2`, i.e. + // the source and target of the link `L2` is changed to point to `A2` and `B2`. + cloneCells: function(cells) { + + cells = joint.util.uniq(cells); + + // A map of the form [original cell ID] -> [clone] helping + // us to reconstruct references for source/target and parent/embeds. + // This is also the returned value. + var cloneMap = joint.util.toArray(cells).reduce(function(map, cell) { + map[cell.id] = cell.clone(); + return map; + }, {}); + + joint.util.toArray(cells).forEach(function(cell) { + + var clone = cloneMap[cell.id]; + // assert(clone exists) + + if (clone.isLink()) { + var source = clone.get('source'); + var target = clone.get('target'); + if (source.id && cloneMap[source.id]) { + // Source points to an element and the element is among the clones. + // => Update the source of the cloned link. + clone.prop('source/id', cloneMap[source.id].id); + } + if (target.id && cloneMap[target.id]) { + // Target points to an element and the element is among the clones. + // => Update the target of the cloned link. + clone.prop('target/id', cloneMap[target.id].id); + } + } + + // Find the parent of the original cell + var parent = cell.get('parent'); + if (parent && cloneMap[parent]) { + clone.set('parent', cloneMap[parent].id); + } + + // Find the embeds of the original cell + var embeds = joint.util.toArray(cell.get('embeds')).reduce(function(newEmbeds, embed) { + // Embedded cells that are not being cloned can not be carried + // over with other embedded cells. + if (cloneMap[embed]) { + newEmbeds.push(cloneMap[embed].id); + } + return newEmbeds; + }, []); + + if (!joint.util.isEmpty(embeds)) { + clone.set('embeds', embeds); + } + }); + + return cloneMap; + }, + + // Clone the whole subgraph (including all the connected links whose source/target is in the subgraph). + // If `opt.deep` is `true`, also take into account all the embedded cells of all the subgraph cells. + // Return a map of the form: [original cell ID] -> [clone]. + cloneSubgraph: function(cells, opt) { + + var subgraph = this.getSubgraph(cells, opt); + return this.cloneCells(subgraph); + }, + + // Return `cells` and all the connected links that connect cells in the `cells` array. + // If `opt.deep` is `true`, return all the cells including all their embedded cells + // and all the links that connect any of the returned cells. + // For example, for a single shallow element, the result is that very same element. + // For two elements connected with a link: `A --- L ---> B`, the result for + // `getSubgraph([A, B])` is `[A, L, B]`. The same goes for `getSubgraph([L])`, the result is again `[A, L, B]`. + getSubgraph: function(cells, opt) { + + opt = opt || {}; + + var subgraph = []; + // `cellMap` is used for a quick lookup of existance of a cell in the `cells` array. + var cellMap = {}; + var elements = []; + var links = []; + + joint.util.toArray(cells).forEach(function(cell) { + if (!cellMap[cell.id]) { + subgraph.push(cell); + cellMap[cell.id] = cell; + if (cell.isLink()) { + links.push(cell); + } else { + elements.push(cell); + } + } + + if (opt.deep) { + var embeds = cell.getEmbeddedCells({ deep: true }); + embeds.forEach(function(embed) { + if (!cellMap[embed.id]) { + subgraph.push(embed); + cellMap[embed.id] = embed; + if (embed.isLink()) { + links.push(embed); + } else { + elements.push(embed); + } + } + }); + } + }); + + links.forEach(function(link) { + // For links, return their source & target (if they are elements - not points). + var source = link.get('source'); + var target = link.get('target'); + if (source.id && !cellMap[source.id]) { + var sourceElement = this.getCell(source.id); + subgraph.push(sourceElement); + cellMap[sourceElement.id] = sourceElement; + elements.push(sourceElement); + } + if (target.id && !cellMap[target.id]) { + var targetElement = this.getCell(target.id); + subgraph.push(this.getCell(target.id)); + cellMap[targetElement.id] = targetElement; + elements.push(targetElement); + } + }, this); + + elements.forEach(function(element) { + // For elements, include their connected links if their source/target is in the subgraph; + var links = this.getConnectedLinks(element, opt); + links.forEach(function(link) { + var source = link.get('source'); + var target = link.get('target'); + if (!cellMap[link.id] && source.id && cellMap[source.id] && target.id && cellMap[target.id]) { + subgraph.push(link); + cellMap[link.id] = link; + } + }); + }, this); + + return subgraph; + }, + + // Find all the predecessors of `element`. This is a reverse operation of `getSuccessors()`. + // If `opt.deep` is `true`, take into account embedded elements too. + // If `opt.breadthFirst` is `true`, use the Breadth-first search algorithm, otherwise use Depth-first search. + getPredecessors: function(element, opt) { + + opt = opt || {}; + var res = []; + // Modify the options so that it includes the `inbound` neighbors only. In other words, search backwards. + this.search(element, function(el) { + if (el !== element) { + res.push(el); + } + }, joint.util.assign({}, opt, { inbound: true })); + return res; + }, + + // Perform search on the graph. + // If `opt.breadthFirst` is `true`, use the Breadth-first Search algorithm, otherwise use Depth-first search. + // By setting `opt.inbound` to `true`, you can reverse the direction of the search. + // If `opt.deep` is `true`, take into account embedded elements too. + // `iteratee` is a function of the form `function(element) {}`. + // If `iteratee` explicitely returns `false`, the searching stops. + search: function(element, iteratee, opt) { + + opt = opt || {}; + if (opt.breadthFirst) { + this.bfs(element, iteratee, opt); + } else { + this.dfs(element, iteratee, opt); + } + }, + + // Breadth-first search. + // If `opt.deep` is `true`, take into account embedded elements too. + // If `opt.inbound` is `true`, reverse the search direction (it's like reversing all the link directions). + // `iteratee` is a function of the form `function(element, distance) {}`. + // where `element` is the currently visited element and `distance` is the distance of that element + // from the root `element` passed the `bfs()`, i.e. the element we started the search from. + // Note that the `distance` is not the shortest or longest distance, it is simply the number of levels + // crossed till we visited the `element` for the first time. It is especially useful for tree graphs. + // If `iteratee` explicitely returns `false`, the searching stops. + bfs: function(element, iteratee, opt) { + + opt = opt || {}; + var visited = {}; + var distance = {}; + var queue = []; + + queue.push(element); + distance[element.id] = 0; + + while (queue.length > 0) { + var next = queue.shift(); + if (!visited[next.id]) { + visited[next.id] = true; + if (iteratee(next, distance[next.id]) === false) return; + this.getNeighbors(next, opt).forEach(function(neighbor) { + distance[neighbor.id] = distance[next.id] + 1; + queue.push(neighbor); + }); + } + } + }, + + // Depth-first search. + // If `opt.deep` is `true`, take into account embedded elements too. + // If `opt.inbound` is `true`, reverse the search direction (it's like reversing all the link directions). + // `iteratee` is a function of the form `function(element, distance) {}`. + // If `iteratee` explicitely returns `false`, the search stops. + dfs: function(element, iteratee, opt, _visited, _distance) { + + opt = opt || {}; + var visited = _visited || {}; + var distance = _distance || 0; + if (iteratee(element, distance) === false) return; + visited[element.id] = true; + + this.getNeighbors(element, opt).forEach(function(neighbor) { + if (!visited[neighbor.id]) { + this.dfs(neighbor, iteratee, opt, visited, distance + 1); + } + }, this); + }, + + // Get all the roots of the graph. Time complexity: O(|V|). + getSources: function() { + + var sources = []; + joint.util.forIn(this._nodes, function(exists, node) { + if (!this._in[node] || joint.util.isEmpty(this._in[node])) { + sources.push(this.getCell(node)); + } + }.bind(this)); + return sources; + }, + + // Get all the leafs of the graph. Time complexity: O(|V|). + getSinks: function() { + + var sinks = []; + joint.util.forIn(this._nodes, function(exists, node) { + if (!this._out[node] || joint.util.isEmpty(this._out[node])) { + sinks.push(this.getCell(node)); + } + }.bind(this)); + return sinks; + }, + + // Return `true` if `element` is a root. Time complexity: O(1). + isSource: function(element) { + + return !this._in[element.id] || joint.util.isEmpty(this._in[element.id]); + }, + + // Return `true` if `element` is a leaf. Time complexity: O(1). + isSink: function(element) { + + return !this._out[element.id] || joint.util.isEmpty(this._out[element.id]); + }, + + // Return `true` is `elementB` is a successor of `elementA`. Return `false` otherwise. + isSuccessor: function(elementA, elementB) { + + var isSuccessor = false; + this.search(elementA, function(element) { + if (element === elementB && element !== elementA) { + isSuccessor = true; + return false; + } + }, { outbound: true }); + return isSuccessor; + }, + + // Return `true` is `elementB` is a predecessor of `elementA`. Return `false` otherwise. + isPredecessor: function(elementA, elementB) { + + var isPredecessor = false; + this.search(elementA, function(element) { + if (element === elementB && element !== elementA) { + isPredecessor = true; + return false; + } + }, { inbound: true }); + return isPredecessor; + }, + + // Return `true` is `elementB` is a neighbor of `elementA`. Return `false` otherwise. + // `opt.deep` controls whether to take into account embedded elements as well. See `getNeighbors()` + // for more details. + // If `opt.outbound` is set to `true`, return `true` only if `elementB` is a successor neighbor. + // Similarly, if `opt.inbound` is set to `true`, return `true` only if `elementB` is a predecessor neighbor. + isNeighbor: function(elementA, elementB, opt) { + + opt = opt || {}; + + var inbound = opt.inbound; + var outbound = opt.outbound; + if (inbound === undefined && outbound === undefined) { + inbound = outbound = true; + } + + var isNeighbor = false; + + this.getConnectedLinks(elementA, opt).forEach(function(link) { + + var source = link.get('source'); + var target = link.get('target'); + + // Discard if it is a point. + if (inbound && joint.util.has(source, 'id') && source.id === elementB.id) { + isNeighbor = true; + return false; + } + + // Discard if it is a point, or if the neighbor was already added. + if (outbound && joint.util.has(target, 'id') && target.id === elementB.id) { + isNeighbor = true; + return false; + } + }); + + return isNeighbor; + }, + + // Disconnect links connected to the cell `model`. + disconnectLinks: function(model, opt) { + + this.getConnectedLinks(model).forEach(function(link) { + + link.set(link.get('source').id === model.id ? 'source' : 'target', { x: 0, y: 0 }, opt); + }); + }, + + // Remove links connected to the cell `model` completely. + removeLinks: function(model, opt) { + + joint.util.invoke(this.getConnectedLinks(model), 'remove', opt); + }, + + // Find all elements at given point + findModelsFromPoint: function(p) { + + return this.getElements().filter(function(el) { + return el.getBBox().containsPoint(p); + }); + }, + + // Find all elements in given area + findModelsInArea: function(rect, opt) { + + rect = g.rect(rect); + opt = joint.util.defaults(opt || {}, { strict: false }); + + var method = opt.strict ? 'containsRect' : 'intersect'; + + return this.getElements().filter(function(el) { + return rect[method](el.getBBox()); + }); + }, + + // Find all elements under the given element. + findModelsUnderElement: function(element, opt) { + + opt = joint.util.defaults(opt || {}, { searchBy: 'bbox' }); + + var bbox = element.getBBox(); + var elements = (opt.searchBy === 'bbox') + ? this.findModelsInArea(bbox) + : this.findModelsFromPoint(bbox[opt.searchBy]()); + + // don't account element itself or any of its descendents + return elements.filter(function(el) { + return element.id !== el.id && !el.isEmbeddedIn(element); + }); + }, + + + // Return bounding box of all elements. + getBBox: function(cells, opt) { + + return this.getCellsBBox(cells || this.getElements(), opt); + }, + + // Return the bounding box of all cells in array provided. + // Links are being ignored. + getCellsBBox: function(cells, opt) { + + return joint.util.toArray(cells).reduce(function(memo, cell) { + if (cell.isLink()) return memo; + if (memo) { + return memo.union(cell.getBBox(opt)); + } else { + return cell.getBBox(opt); + } + }, null); + }, + + translate: function(dx, dy, opt) { + + // Don't translate cells that are embedded in any other cell. + var cells = this.getCells().filter(function(cell) { + return !cell.isEmbedded(); + }); + + joint.util.invoke(cells, 'translate', dx, dy, opt); + + return this; + }, + + resize: function(width, height, opt) { + + return this.resizeCells(width, height, this.getCells(), opt); + }, + + resizeCells: function(width, height, cells, opt) { + + // `getBBox` method returns `null` if no elements provided. + // i.e. cells can be an array of links + var bbox = this.getCellsBBox(cells); + if (bbox) { + var sx = Math.max(width / bbox.width, 0); + var sy = Math.max(height / bbox.height, 0); + joint.util.invoke(cells, 'scale', sx, sy, bbox.origin(), opt); + } + + return this; + }, + + startBatch: function(name, data) { + + data = data || {}; + this._batches[name] = (this._batches[name] || 0) + 1; + + return this.trigger('batch:start', joint.util.assign({}, data, { batchName: name })); + }, + + stopBatch: function(name, data) { + + data = data || {}; + this._batches[name] = (this._batches[name] || 0) - 1; + + return this.trigger('batch:stop', joint.util.assign({}, data, { batchName: name })); + }, + + hasActiveBatch: function(name) { + if (arguments.length === 0) { + return joint.util.toArray(this._batches).some(function(batches) { + return batches > 0; + }); + } + if (Array.isArray(name)) { + return name.some(function(name) { + return !!this._batches[name]; + }, this); + } + return !!this._batches[name]; + } + +}, { + + validations: { + + multiLinks: function(graph, link) { + + // Do not allow multiple links to have the same source and target. + var source = link.get('source'); + var target = link.get('target'); + + if (source.id && target.id) { + + var sourceModel = link.getSourceElement(); + if (sourceModel) { + + var connectedLinks = graph.getConnectedLinks(sourceModel, { outbound: true }); + var sameLinks = connectedLinks.filter(function(_link) { + + var _source = _link.get('source'); + var _target = _link.get('target'); + + return _source && _source.id === source.id && + (!_source.port || (_source.port === source.port)) && + _target && _target.id === target.id && + (!_target.port || (_target.port === target.port)); + + }); + + if (sameLinks.length > 1) { + return false; + } + } + } + + return true; + }, + + linkPinning: function(graph, link) { + return link.source().id && link.target().id; + } + } + +}); + +joint.util.wrapWith(joint.dia.Graph.prototype, ['resetCells', 'addCells', 'removeCells'], 'cells'); + +(function(joint, V, g, $, util) { + + function setWrapper(attrName, dimension) { + return function(value, refBBox) { + var isValuePercentage = util.isPercentage(value); + value = parseFloat(value); + if (isValuePercentage) { + value /= 100; + } + + var attrs = {}; + if (isFinite(value)) { + var attrValue = (isValuePercentage || value >= 0 && value <= 1) + ? value * refBBox[dimension] + : Math.max(value + refBBox[dimension], 0); + attrs[attrName] = attrValue; + } + + return attrs; + }; + } + + function positionWrapper(axis, dimension, origin) { + return function(value, refBBox) { + var valuePercentage = util.isPercentage(value); + value = parseFloat(value); + if (valuePercentage) { + value /= 100; + } + + var delta; + if (isFinite(value)) { + var refOrigin = refBBox[origin](); + if (valuePercentage || value > 0 && value < 1) { + delta = refOrigin[axis] + refBBox[dimension] * value; + } else { + delta = refOrigin[axis] + value; + } + } + + var point = g.Point(); + point[axis] = delta || 0; + return point; + }; + } + + function offsetWrapper(axis, dimension, corner) { + return function(value, nodeBBox) { + var delta; + if (value === 'middle') { + delta = nodeBBox[dimension] / 2; + } else if (value === corner) { + delta = nodeBBox[dimension]; + } else if (isFinite(value)) { + // TODO: or not to do a breaking change? + delta = (value > -1 && value < 1) ? (-nodeBBox[dimension] * value) : -value; + } else if (util.isPercentage(value)) { + delta = nodeBBox[dimension] * parseFloat(value) / 100; + } else { + delta = 0; + } + + var point = g.Point(); + point[axis] = -(nodeBBox[axis] + delta); + return point; + }; + } + + function shapeWrapper(shapeConstructor, opt) { + var cacheName = 'joint-shape'; + var resetOffset = opt && opt.resetOffset; + return function(value, refBBox, node) { + var $node = $(node); + var cache = $node.data(cacheName); + if (!cache || cache.value !== value) { + // only recalculate if value has changed + var cachedShape = shapeConstructor(value); + cache = { + value: value, + shape: cachedShape, + shapeBBox: cachedShape.bbox() + }; + $node.data(cacheName, cache); + } + + var shape = cache.shape.clone(); + var shapeBBox = cache.shapeBBox.clone(); + var shapeOrigin = shapeBBox.origin(); + var refOrigin = refBBox.origin(); + + shapeBBox.x = refOrigin.x; + shapeBBox.y = refOrigin.y; + + var fitScale = refBBox.maxRectScaleToFit(shapeBBox, refOrigin); + // `maxRectScaleToFit` can give Infinity if width or height is 0 + var sx = (shapeBBox.width === 0 || refBBox.width === 0) ? 1 : fitScale.sx; + var sy = (shapeBBox.height === 0 || refBBox.height === 0) ? 1 : fitScale.sy; + + shape.scale(sx, sy, shapeOrigin); + if (resetOffset) { + shape.translate(-shapeOrigin.x, -shapeOrigin.y); + } + + return shape; + }; + } + + // `d` attribute for SVGPaths + function dWrapper(opt) { + function pathConstructor(value) { + return new g.Path(V.normalizePathData(value)); + } + var shape = shapeWrapper(pathConstructor, opt); + return function(value, refBBox, node) { + var path = shape(value, refBBox, node); + return { + d: path.serialize() + }; + }; + } + + // `points` attribute for SVGPolylines and SVGPolygons + function pointsWrapper(opt) { + var shape = shapeWrapper(g.Polyline, opt); + return function(value, refBBox, node) { + var polyline = shape(value, refBBox, node); + return { + points: polyline.serialize() + }; + }; + } + + function atConnectionWrapper(method, opt) { + var zeroVector = new g.Point(1, 0); + return function(value) { + var p, angle; + var tangent = this[method](value); + if (tangent) { + angle = (opt.rotate) ? tangent.vector().vectorAngle(zeroVector) : 0; + p = tangent.start; + } else { + p = this.path.start; + angle = 0; + } + if (angle === 0) return { transform: 'translate(' + p.x + ',' + p.y + ')' }; + return { transform: 'translate(' + p.x + ',' + p.y + ') rotate(' + angle + ')' }; + } + } + + function isTextInUse(lineHeight, node, attrs) { + return (attrs.text !== undefined); + } + + function isLinkView() { + return this instanceof joint.dia.LinkView; + } + + function contextMarker(context) { + var marker = {}; + // Stroke + // The context 'fill' is disregared here. The usual case is to use the marker with a connection + // (for which 'fill' attribute is set to 'none'). + var stroke = context.stroke; + if (typeof stroke === 'string') { + marker['stroke'] = stroke; + marker['fill'] = stroke; + } + // Opacity + // Again the context 'fill-opacity' is ignored. + var strokeOpacity = context.strokeOpacity; + if (strokeOpacity === undefined) strokeOpacity = context['stroke-opacity']; + if (strokeOpacity === undefined) strokeOpacity = context.opacity + if (strokeOpacity !== undefined) { + marker['stroke-opacity'] = strokeOpacity; + marker['fill-opacity'] = strokeOpacity; + } + return marker; + } + + var attributesNS = joint.dia.attributes = { + + xlinkHref: { + set: 'xlink:href' + }, + + xlinkShow: { + set: 'xlink:show' + }, + + xlinkRole: { + set: 'xlink:role' + }, + + xlinkType: { + set: 'xlink:type' + }, + + xlinkArcrole: { + set: 'xlink:arcrole' + }, + + xlinkTitle: { + set: 'xlink:title' + }, + + xlinkActuate: { + set: 'xlink:actuate' + }, + + xmlSpace: { + set: 'xml:space' + }, + + xmlBase: { + set: 'xml:base' + }, + + xmlLang: { + set: 'xml:lang' + }, + + preserveAspectRatio: { + set: 'preserveAspectRatio' + }, + + requiredExtension: { + set: 'requiredExtension' + }, + + requiredFeatures: { + set: 'requiredFeatures' + }, + + systemLanguage: { + set: 'systemLanguage' + }, + + externalResourcesRequired: { + set: 'externalResourceRequired' + }, + + filter: { + qualify: util.isPlainObject, + set: function(filter) { + return 'url(#' + this.paper.defineFilter(filter) + ')'; + } + }, + + fill: { + qualify: util.isPlainObject, + set: function(fill) { + return 'url(#' + this.paper.defineGradient(fill) + ')'; + } + }, + + stroke: { + qualify: util.isPlainObject, + set: function(stroke) { + return 'url(#' + this.paper.defineGradient(stroke) + ')'; + } + }, + + sourceMarker: { + qualify: util.isPlainObject, + set: function(marker, refBBox, node, attrs) { + marker = util.assign(contextMarker(attrs), marker); + return { 'marker-start': 'url(#' + this.paper.defineMarker(marker) + ')' }; + } + }, + + targetMarker: { + qualify: util.isPlainObject, + set: function(marker, refBBox, node, attrs) { + marker = util.assign(contextMarker(attrs), { 'transform': 'rotate(180)' }, marker); + return { 'marker-end': 'url(#' + this.paper.defineMarker(marker) + ')' }; + } + }, + + vertexMarker: { + qualify: util.isPlainObject, + set: function(marker, refBBox, node, attrs) { + marker = util.assign(contextMarker(attrs), marker); + return { 'marker-mid': 'url(#' + this.paper.defineMarker(marker) + ')' }; + } + }, + + text: { + qualify: function(text, node, attrs) { + return !attrs.textWrap || !util.isPlainObject(attrs.textWrap); + }, + set: function(text, refBBox, node, attrs) { + var $node = $(node); + var cacheName = 'joint-text'; + var cache = $node.data(cacheName); + var textAttrs = joint.util.pick(attrs, 'lineHeight', 'annotations', 'textPath', 'x', 'textVerticalAnchor', 'eol'); + var fontSize = textAttrs.fontSize = attrs['font-size'] || attrs['fontSize']; + var textHash = JSON.stringify([text, textAttrs]); + // Update the text only if there was a change in the string + // or any of its attributes. + if (cache === undefined || cache !== textHash) { + // Chrome bug: + // Tspans positions defined as `em` are not updated + // when container `font-size` change. + if (fontSize) node.setAttribute('font-size', fontSize); + // Text Along Path Selector + var textPath = textAttrs.textPath; + if (util.isObject(textPath)) { + var pathSelector = textPath.selector; + if (typeof pathSelector === 'string') { + var pathNode = this.findBySelector(pathSelector)[0]; + if (pathNode instanceof SVGPathElement) { + textAttrs.textPath = util.assign({ 'xlink:href': '#' + pathNode.id }, textPath); + } + } + } + V(node).text('' + text, textAttrs); + $node.data(cacheName, textHash); + } + } + }, + + textWrap: { + qualify: util.isPlainObject, + set: function(value, refBBox, node, attrs) { + // option `width` + var width = value.width || 0; + if (util.isPercentage(width)) { + refBBox.width *= parseFloat(width) / 100; + } else if (width <= 0) { + refBBox.width += width; + } else { + refBBox.width = width; + } + // option `height` + var height = value.height || 0; + if (util.isPercentage(height)) { + refBBox.height *= parseFloat(height) / 100; + } else if (height <= 0) { + refBBox.height += height; + } else { + refBBox.height = height; + } + // option `text` + var text = value.text; + if (text === undefined) text = attr.text; + if (text !== undefined) { + var wrappedText = joint.util.breakText('' + text, refBBox, { + 'font-weight': attrs['font-weight'] || attrs.fontWeight, + 'font-size': attrs['font-size'] || attrs.fontSize, + 'font-family': attrs['font-family'] || attrs.fontFamily, + 'lineHeight': attrs.lineHeight + }, { + // Provide an existing SVG Document here + // instead of creating a temporary one over again. + svgDocument: this.paper.svg + }); + } + joint.dia.attributes.text.set.call(this, wrappedText, refBBox, node, attrs); + } + }, + + title: { + qualify: function(title, node) { + // HTMLElement title is specified via an attribute (i.e. not an element) + return node instanceof SVGElement; + }, + set: function(title, refBBox, node) { + var $node = $(node); + var cacheName = 'joint-title'; + var cache = $node.data(cacheName); + if (cache === undefined || cache !== title) { + $node.data(cacheName, title); + // Generally element should be the first child element of its parent. + var firstChild = node.firstChild; + if (firstChild && firstChild.tagName.toUpperCase() === 'TITLE') { + // Update an existing title + firstChild.textContent = title; + } else { + // Create a new title + var titleNode = document.createElementNS(node.namespaceURI, 'title'); + titleNode.textContent = title; + node.insertBefore(titleNode, firstChild); + } + } + } + }, + + lineHeight: { + qualify: isTextInUse + }, + + textVerticalAnchor: { + qualify: isTextInUse + }, + + textPath: { + qualify: isTextInUse + }, + + annotations: { + qualify: isTextInUse + }, + + // `port` attribute contains the `id` of the port that the underlying magnet represents. + port: { + set: function(port) { + return (port === null || port.id === undefined) ? port : port.id; + } + }, + + // `style` attribute is special in the sense that it sets the CSS style of the subelement. + style: { + qualify: util.isPlainObject, + set: function(styles, refBBox, node) { + $(node).css(styles); + } + }, + + html: { + set: function(html, refBBox, node) { + $(node).html(html + ''); + } + }, + + ref: { + // We do not set `ref` attribute directly on an element. + // The attribute itself does not qualify for relative positioning. + }, + + // if `refX` is in [0, 1] then `refX` is a fraction of bounding box width + // if `refX` is < 0 then `refX`'s absolute values is the right coordinate of the bounding box + // otherwise, `refX` is the left coordinate of the bounding box + + refX: { + position: positionWrapper('x', 'width', 'origin') + }, + + refY: { + position: positionWrapper('y', 'height', 'origin') + }, + + // `ref-dx` and `ref-dy` define the offset of the subelement relative to the right and/or bottom + // coordinate of the reference element. + + refDx: { + position: positionWrapper('x', 'width', 'corner') + }, + + refDy: { + position: positionWrapper('y', 'height', 'corner') + }, + + // 'ref-width'/'ref-height' defines the width/height of the subelement relatively to + // the reference element size + // val in 0..1 ref-width = 0.75 sets the width to 75% of the ref. el. width + // val < 0 || val > 1 ref-height = -20 sets the height to the the ref. el. height shorter by 20 + + refWidth: { + set: setWrapper('width', 'width') + }, + + refHeight: { + set: setWrapper('height', 'height') + }, + + refRx: { + set: setWrapper('rx', 'width') + }, + + refRy: { + set: setWrapper('ry', 'height') + }, + + refRInscribed: { + set: (function(attrName) { + var widthFn = setWrapper(attrName, 'width'); + var heightFn = setWrapper(attrName, 'height'); + return function(value, refBBox) { + var fn = (refBBox.height > refBBox.width) ? widthFn : heightFn; + return fn(value, refBBox); + } + })('r') + }, + + refRCircumscribed: { + set: function(value, refBBox) { + var isValuePercentage = util.isPercentage(value); + value = parseFloat(value); + if (isValuePercentage) { + value /= 100; + } + + var diagonalLength = Math.sqrt((refBBox.height * refBBox.height) + (refBBox.width * refBBox.width)); + + var rValue; + if (isFinite(value)) { + if (isValuePercentage || value >= 0 && value <= 1) rValue = value * diagonalLength; + else rValue = Math.max(value + diagonalLength, 0); + } + + return { r: rValue }; + } + }, + + refCx: { + set: setWrapper('cx', 'width') + }, + + refCy: { + set: setWrapper('cy', 'height') + }, + + // `x-alignment` when set to `middle` causes centering of the subelement around its new x coordinate. + // `x-alignment` when set to `right` uses the x coordinate as referenced to the right of the bbox. + + xAlignment: { + offset: offsetWrapper('x', 'width', 'right') + }, + + // `y-alignment` when set to `middle` causes centering of the subelement around its new y coordinate. + // `y-alignment` when set to `bottom` uses the y coordinate as referenced to the bottom of the bbox. + + yAlignment: { + offset: offsetWrapper('y', 'height', 'bottom') + }, + + resetOffset: { + offset: function(val, nodeBBox) { + return (val) + ? { x: -nodeBBox.x, y: -nodeBBox.y } + : { x: 0, y: 0 }; + } + + }, + + refDResetOffset: { + set: dWrapper({ resetOffset: true }) + }, + + refDKeepOffset: { + set: dWrapper({ resetOffset: false }) + }, + + refPointsResetOffset: { + set: pointsWrapper({ resetOffset: true }) + }, + + refPointsKeepOffset: { + set: pointsWrapper({ resetOffset: false }) + }, + + // LinkView Attributes + + connection: { + qualify: isLinkView, + set: function() { + return { d: this.getSerializedConnection() }; + } + }, + + atConnectionLengthKeepGradient: { + qualify: isLinkView, + set: atConnectionWrapper('getTangentAtLength', { rotate: true }) + }, + + atConnectionLengthIgnoreGradient: { + qualify: isLinkView, + set: atConnectionWrapper('getTangentAtLength', { rotate: false }) + }, + + atConnectionRatioKeepGradient: { + qualify: isLinkView, + set: atConnectionWrapper('getTangentAtRatio', { rotate: true }) + }, + + atConnectionRatioIgnoreGradient: { + qualify: isLinkView, + set: atConnectionWrapper('getTangentAtRatio', { rotate: false }) + } + }; + + // Aliases + attributesNS.refR = attributesNS.refRInscribed; + attributesNS.refD = attributesNS.refDResetOffset; + attributesNS.refPoints = attributesNS.refPointsResetOffset; + attributesNS.atConnectionLength = attributesNS.atConnectionLengthKeepGradient; + attributesNS.atConnectionRatio = attributesNS.atConnectionRatioKeepGradient; + + // This allows to combine both absolute and relative positioning + // refX: 50%, refX2: 20 + attributesNS.refX2 = attributesNS.refX; + attributesNS.refY2 = attributesNS.refY; + + // Aliases for backwards compatibility + attributesNS['ref-x'] = attributesNS.refX; + attributesNS['ref-y'] = attributesNS.refY; + attributesNS['ref-dy'] = attributesNS.refDy; + attributesNS['ref-dx'] = attributesNS.refDx; + attributesNS['ref-width'] = attributesNS.refWidth; + attributesNS['ref-height'] = attributesNS.refHeight; + attributesNS['x-alignment'] = attributesNS.xAlignment; + attributesNS['y-alignment'] = attributesNS.yAlignment; + +})(joint, V, g, $, joint.util); + +(function(joint, util) { + + var ToolView = joint.mvc.View.extend({ + name: null, + tagName: 'g', + className: 'tool', + svgElement: true, + _visible: true, + + init: function() { + var name = this.name; + if (name) this.vel.attr('data-tool-name', name); + }, + + configure: function(view, toolsView) { + this.relatedView = view; + this.paper = view.paper; + this.parentView = toolsView; + this.simulateRelatedView(this.el); + return this; + }, + + simulateRelatedView: function(el) { + if (el) el.setAttribute('model-id', this.relatedView.model.id); + }, + + getName: function() { + return this.name; + }, + + show: function() { + this.el.style.display = ''; + this._visible = true; + }, + + hide: function() { + this.el.style.display = 'none'; + this._visible = false; + }, + + isVisible: function() { + return !!this._visible; + }, + + focus: function() { + var opacity = this.options.focusOpacity; + if (isFinite(opacity)) this.el.style.opacity = opacity; + this.parentView.focusTool(this); + }, + + blur: function() { + this.el.style.opacity = ''; + this.parentView.blurTool(this); + }, + + update: function() { + // to be overriden + } + }); + + var ToolsView = joint.mvc.View.extend({ + tagName: 'g', + className: 'tools', + svgElement: true, + tools: null, + options: { + tools: null, + relatedView: null, + name: null, + component: false + }, + + configure: function(options) { + options = util.assign(this.options, options); + var tools = options.tools; + if (!Array.isArray(tools)) return this; + var relatedView = options.relatedView; + if (!(relatedView instanceof joint.dia.CellView)) return this; + var views = this.tools = []; + for (var i = 0, n = tools.length; i < n; i++) { + var tool = tools[i]; + if (!(tool instanceof ToolView)) continue; + tool.configure(relatedView, this); + tool.render(); + this.vel.append(tool.el); + views.push(tool); + } + return this; + }, + + getName: function() { + return this.options.name; + }, + + update: function(opt) { + + opt || (opt = {}); + var tools = this.tools; + if (!tools) return; + for (var i = 0, n = tools.length; i < n; i++) { + var tool = tools[i]; + if (opt.tool !== tool.cid && tool.isVisible()) { + tool.update(); + } + } + return this; + }, + + focusTool: function(focusedTool) { + + var tools = this.tools; + if (!tools) return this; + for (var i = 0, n = tools.length; i < n; i++) { + var tool = tools[i]; + if (focusedTool === tool) { + tool.show(); + } else { + tool.hide(); + } + } + return this; + }, + + blurTool: function(blurredTool) { + var tools = this.tools; + if (!tools) return this; + for (var i = 0, n = tools.length; i < n; i++) { + var tool = tools[i]; + if (tool !== blurredTool && !tool.isVisible()) { + tool.show(); + tool.update(); + } + } + return this; + }, + + hide: function() { + return this.focusTool(null); + }, + + show: function() { + return this.blurTool(null); + }, + + onRemove: function() { + + var tools = this.tools; + if (!tools) return this; + for (var i = 0, n = tools.length; i < n; i++) { + tools[i].remove(); + } + this.tools = null; + }, + + mount: function() { + var options = this.options; + var relatedView = options.relatedView; + if (relatedView) { + var container = (options.component) ? relatedView.el : relatedView.paper.tools; + container.appendChild(this.el); + } + return this; + } + + }); + + joint.dia.ToolsView = ToolsView; + joint.dia.ToolView = ToolView; + +})(joint, joint.util); + + +// joint.dia.Cell base model. +// -------------------------- + +joint.dia.Cell = Backbone.Model.extend({ + + // This is the same as Backbone.Model with the only difference that is uses joint.util.merge + // instead of just _.extend. The reason is that we want to mixin attributes set in upper classes. + constructor: function(attributes, options) { + + var defaults; + var attrs = attributes || {}; + this.cid = joint.util.uniqueId('c'); + this.attributes = {}; + if (options && options.collection) this.collection = options.collection; + if (options && options.parse) attrs = this.parse(attrs, options) || {}; + if ((defaults = joint.util.result(this, 'defaults'))) { + //<custom code> + // Replaced the call to _.defaults with joint.util.merge. + attrs = joint.util.merge({}, defaults, attrs); + //</custom code> + } + this.set(attrs, options); + this.changed = {}; + this.initialize.apply(this, arguments); + }, + + translate: function(dx, dy, opt) { + + throw new Error('Must define a translate() method.'); + }, + + toJSON: function() { + + var defaultAttrs = this.constructor.prototype.defaults.attrs || {}; + var attrs = this.attributes.attrs; + var finalAttrs = {}; + + // Loop through all the attributes and + // omit the default attributes as they are implicitly reconstructable by the cell 'type'. + joint.util.forIn(attrs, function(attr, selector) { + + var defaultAttr = defaultAttrs[selector]; + + joint.util.forIn(attr, function(value, name) { + + // attr is mainly flat though it might have one more level (consider the `style` attribute). + // Check if the `value` is object and if yes, go one level deep. + if (joint.util.isObject(value) && !Array.isArray(value)) { + + joint.util.forIn(value, function(value2, name2) { + + if (!defaultAttr || !defaultAttr[name] || !joint.util.isEqual(defaultAttr[name][name2], value2)) { + + finalAttrs[selector] = finalAttrs[selector] || {}; + (finalAttrs[selector][name] || (finalAttrs[selector][name] = {}))[name2] = value2; + } + }); + + } else if (!defaultAttr || !joint.util.isEqual(defaultAttr[name], value)) { + // `value` is not an object, default attribute for such a selector does not exist + // or it is different than the attribute value set on the model. + + finalAttrs[selector] = finalAttrs[selector] || {}; + finalAttrs[selector][name] = value; + } + }); + }); + + var attributes = joint.util.cloneDeep(joint.util.omit(this.attributes, 'attrs')); + //var attributes = JSON.parse(JSON.stringify(_.omit(this.attributes, 'attrs'))); + attributes.attrs = finalAttrs; + + return attributes; + }, + + initialize: function(options) { + + if (!options || !options.id) { + + this.set('id', joint.util.uuid(), { silent: true }); + } + + this._transitionIds = {}; + + // Collect ports defined in `attrs` and keep collecting whenever `attrs` object changes. + this.processPorts(); + this.on('change:attrs', this.processPorts, this); + }, + + /** + * @deprecated + */ + processPorts: function() { + + // Whenever `attrs` changes, we extract ports from the `attrs` object and store it + // in a more accessible way. Also, if any port got removed and there were links that had `target`/`source` + // set to that port, we remove those links as well (to follow the same behaviour as + // with a removed element). + + var previousPorts = this.ports; + + // Collect ports from the `attrs` object. + var ports = {}; + joint.util.forIn(this.get('attrs'), function(attrs, selector) { + + if (attrs && attrs.port) { + + // `port` can either be directly an `id` or an object containing an `id` (and potentially other data). + if (attrs.port.id !== undefined) { + ports[attrs.port.id] = attrs.port; + } else { + ports[attrs.port] = { id: attrs.port }; + } + } + }); + + // Collect ports that have been removed (compared to the previous ports) - if any. + // Use hash table for quick lookup. + var removedPorts = {}; + joint.util.forIn(previousPorts, function(port, id) { + + if (!ports[id]) removedPorts[id] = true; + }); + + // Remove all the incoming/outgoing links that have source/target port set to any of the removed ports. + if (this.graph && !joint.util.isEmpty(removedPorts)) { + + var inboundLinks = this.graph.getConnectedLinks(this, { inbound: true }); + inboundLinks.forEach(function(link) { + + if (removedPorts[link.get('target').port]) link.remove(); + }); + + var outboundLinks = this.graph.getConnectedLinks(this, { outbound: true }); + outboundLinks.forEach(function(link) { + + if (removedPorts[link.get('source').port]) link.remove(); + }); + } + + // Update the `ports` object. + this.ports = ports; + }, + + remove: function(opt) { + + opt = opt || {}; + + // Store the graph in a variable because `this.graph` won't' be accessbile after `this.trigger('remove', ...)` down below. + var graph = this.graph; + if (graph) { + graph.startBatch('remove'); + } + + // First, unembed this cell from its parent cell if there is one. + var parentCell = this.getParentCell(); + if (parentCell) parentCell.unembed(this); + + joint.util.invoke(this.getEmbeddedCells(), 'remove', opt); + + this.trigger('remove', this, this.collection, opt); + + if (graph) { + graph.stopBatch('remove'); + } + + return this; + }, + + toFront: function(opt) { + + var graph = this.graph; + if (graph) { + + opt = opt || {}; + + var z = graph.maxZIndex(); + + var cells; + + if (opt.deep) { + cells = this.getEmbeddedCells({ deep: true, breadthFirst: true }); + cells.unshift(this); + } else { + cells = [this]; + } + + z = z - cells.length + 1; + + var collection = graph.get('cells'); + var shouldUpdate = (collection.indexOf(this) !== (collection.length - cells.length)); + if (!shouldUpdate) { + shouldUpdate = cells.some(function(cell, index) { + return cell.get('z') !== z + index; + }); + } + + if (shouldUpdate) { + this.startBatch('to-front'); + + z = z + cells.length; + + cells.forEach(function(cell, index) { + cell.set('z', z + index, opt); + }); + + this.stopBatch('to-front'); + } + } + + return this; + }, + + toBack: function(opt) { + + var graph = this.graph; + if (graph) { + + opt = opt || {}; + + var z = graph.minZIndex(); + + var cells; + + if (opt.deep) { + cells = this.getEmbeddedCells({ deep: true, breadthFirst: true }); + cells.unshift(this); + } else { + cells = [this]; + } + + var collection = graph.get('cells'); + var shouldUpdate = (collection.indexOf(this) !== 0); + if (!shouldUpdate) { + shouldUpdate = cells.some(function(cell, index) { + return cell.get('z') !== z + index; + }); + } + + if (shouldUpdate) { + this.startBatch('to-back'); + + z -= cells.length; + + cells.forEach(function(cell, index) { + cell.set('z', z + index, opt); + }); + + this.stopBatch('to-back'); + } + } + + return this; + }, + + parent: function(parent, opt) { + + // getter + if (parent === undefined) return this.get('parent'); + // setter + return this.set('parent', parent, opt); + }, + + embed: function(cell, opt) { + + if (this === cell || this.isEmbeddedIn(cell)) { + + throw new Error('Recursive embedding not allowed.'); + + } else { + + this.startBatch('embed'); + + var embeds = joint.util.assign([], this.get('embeds')); + + // We keep all element ids after link ids. + embeds[cell.isLink() ? 'unshift' : 'push'](cell.id); + + cell.parent(this.id, opt); + this.set('embeds', joint.util.uniq(embeds), opt); + + this.stopBatch('embed'); + } + + return this; + }, + + unembed: function(cell, opt) { + + this.startBatch('unembed'); + + cell.unset('parent', opt); + this.set('embeds', joint.util.without(this.get('embeds'), cell.id), opt); + + this.stopBatch('unembed'); + + return this; + }, + + getParentCell: function() { + + // unlike link.source/target, cell.parent stores id directly as a string + var parentId = this.parent(); + var graph = this.graph; + + return (parentId && graph && graph.getCell(parentId)) || null; + }, + + // Return an array of ancestor cells. + // The array is ordered from the parent of the cell + // to the most distant ancestor. + getAncestors: function() { + + var ancestors = []; + + if (!this.graph) { + return ancestors; + } + + var parentCell = this.getParentCell(); + while (parentCell) { + ancestors.push(parentCell); + parentCell = parentCell.getParentCell(); + } + + return ancestors; + }, + + getEmbeddedCells: function(opt) { + + opt = opt || {}; + + // Cell models can only be retrieved when this element is part of a collection. + // There is no way this element knows about other cells otherwise. + // This also means that calling e.g. `translate()` on an element with embeds before + // adding it to a graph does not translate its embeds. + if (this.graph) { + + var cells; + + if (opt.deep) { + + if (opt.breadthFirst) { + + // breadthFirst algorithm + cells = []; + var queue = this.getEmbeddedCells(); + + while (queue.length > 0) { + + var parent = queue.shift(); + cells.push(parent); + queue.push.apply(queue, parent.getEmbeddedCells()); + } + + } else { + + // depthFirst algorithm + cells = this.getEmbeddedCells(); + cells.forEach(function(cell) { + cells.push.apply(cells, cell.getEmbeddedCells(opt)); + }); + } + + } else { + + cells = joint.util.toArray(this.get('embeds')).map(this.graph.getCell, this.graph); + } + + return cells; + } + return []; + }, + + isEmbeddedIn: function(cell, opt) { + + var cellId = joint.util.isString(cell) ? cell : cell.id; + var parentId = this.parent(); + + opt = joint.util.defaults({ deep: true }, opt); + + // See getEmbeddedCells(). + if (this.graph && opt.deep) { + + while (parentId) { + if (parentId === cellId) { + return true; + } + parentId = this.graph.getCell(parentId).parent(); + } + + return false; + + } else { + + // When this cell is not part of a collection check + // at least whether it's a direct child of given cell. + return parentId === cellId; + } + }, + + // Whether or not the cell is embedded in any other cell. + isEmbedded: function() { + + return !!this.parent(); + }, + + // Isolated cloning. Isolated cloning has two versions: shallow and deep (pass `{ deep: true }` in `opt`). + // Shallow cloning simply clones the cell and returns a new cell with different ID. + // Deep cloning clones the cell and all its embedded cells recursively. + clone: function(opt) { + + opt = opt || {}; + + if (!opt.deep) { + // Shallow cloning. + + var clone = Backbone.Model.prototype.clone.apply(this, arguments); + // We don't want the clone to have the same ID as the original. + clone.set('id', joint.util.uuid()); + // A shallow cloned element does not carry over the original embeds. + clone.unset('embeds'); + // And can not be embedded in any cell + // as the clone is not part of the graph. + clone.unset('parent'); + + return clone; + + } else { + // Deep cloning. + + // For a deep clone, simply call `graph.cloneCells()` with the cell and all its embedded cells. + return joint.util.toArray(joint.dia.Graph.prototype.cloneCells.call(null, [this].concat(this.getEmbeddedCells({ deep: true })))); + } + }, + + // A convenient way to set nested properties. + // This method merges the properties you'd like to set with the ones + // stored in the cell and makes sure change events are properly triggered. + // You can either set a nested property with one object + // or use a property path. + // The most simple use case is: + // `cell.prop('name/first', 'John')` or + // `cell.prop({ name: { first: 'John' } })`. + // Nested arrays are supported too: + // `cell.prop('series/0/data/0/degree', 50)` or + // `cell.prop({ series: [ { data: [ { degree: 50 } ] } ] })`. + prop: function(props, value, opt) { + + var delim = '/'; + var isString = joint.util.isString(props); + + if (isString || Array.isArray(props)) { + // Get/set an attribute by a special path syntax that delimits + // nested objects by the colon character. + + if (arguments.length > 1) { + + var path; + var pathArray; + + if (isString) { + path = props; + pathArray = path.split('/') + } else { + path = props.join(delim); + pathArray = props.slice(); + } + + var property = pathArray[0]; + var pathArrayLength = pathArray.length; + + opt = opt || {}; + opt.propertyPath = path; + opt.propertyValue = value; + opt.propertyPathArray = pathArray; + + if (pathArrayLength === 1) { + // Property is not nested. We can simply use `set()`. + return this.set(property, value, opt); + } + + var update = {}; + // Initialize the nested object. Subobjects are either arrays or objects. + // An empty array is created if the sub-key is an integer. Otherwise, an empty object is created. + // Note that this imposes a limitation on object keys one can use with Inspector. + // Pure integer keys will cause issues and are therefore not allowed. + var initializer = update; + var prevProperty = property; + + for (var i = 1; i < pathArrayLength; i++) { + var pathItem = pathArray[i]; + var isArrayIndex = Number.isFinite(isString ? Number(pathItem) : pathItem); + initializer = initializer[prevProperty] = isArrayIndex ? [] : {}; + prevProperty = pathItem; + } + + // Fill update with the `value` on `path`. + update = joint.util.setByPath(update, pathArray, value, '/'); + + var baseAttributes = joint.util.merge({}, this.attributes); + // if rewrite mode enabled, we replace value referenced by path with + // the new one (we don't merge). + opt.rewrite && joint.util.unsetByPath(baseAttributes, path, '/'); + + // Merge update with the model attributes. + var attributes = joint.util.merge(baseAttributes, update); + // Finally, set the property to the updated attributes. + return this.set(property, attributes[property], opt); + + } else { + + return joint.util.getByPath(this.attributes, props, delim); + } + } + + return this.set(joint.util.merge({}, this.attributes, props), value); + }, + + // A convient way to unset nested properties + removeProp: function(path, opt) { + + // Once a property is removed from the `attrs` attribute + // the cellView will recognize a `dirty` flag and rerender itself + // in order to remove the attribute from SVG element. + opt = opt || {}; + opt.dirty = true; + + var pathArray = Array.isArray(path) ? path : path.split('/'); + + if (pathArray.length === 1) { + // A top level property + return this.unset(path, opt); + } + + // A nested property + var property = pathArray[0]; + var nestedPath = pathArray.slice(1); + var propertyValue = joint.util.cloneDeep(this.get(property)); + + joint.util.unsetByPath(propertyValue, nestedPath, '/'); + + return this.set(property, propertyValue, opt); + }, + + // A convenient way to set nested attributes. + attr: function(attrs, value, opt) { + + var args = Array.from(arguments); + if (args.length === 0) { + return this.get('attrs'); + } + + if (Array.isArray(attrs)) { + args[0] = ['attrs'].concat(attrs); + } else if (joint.util.isString(attrs)) { + // Get/set an attribute by a special path syntax that delimits + // nested objects by the colon character. + args[0] = 'attrs/' + attrs; + + } else { + + args[0] = { 'attrs' : attrs }; + } + + return this.prop.apply(this, args); + }, + + // A convenient way to unset nested attributes + removeAttr: function(path, opt) { + + if (Array.isArray(path)) { + + return this.removeProp(['attrs'].concat(path)); + } + + return this.removeProp('attrs/' + path, opt); + }, + + transition: function(path, value, opt, delim) { + + delim = delim || '/'; + + var defaults = { + duration: 100, + delay: 10, + timingFunction: joint.util.timing.linear, + valueFunction: joint.util.interpolate.number + }; + + opt = joint.util.assign(defaults, opt); + + var firstFrameTime = 0; + var interpolatingFunction; + + var setter = function(runtime) { + + var id, progress, propertyValue; + + firstFrameTime = firstFrameTime || runtime; + runtime -= firstFrameTime; + progress = runtime / opt.duration; + + if (progress < 1) { + this._transitionIds[path] = id = joint.util.nextFrame(setter); + } else { + progress = 1; + delete this._transitionIds[path]; + } + + propertyValue = interpolatingFunction(opt.timingFunction(progress)); + + opt.transitionId = id; + + this.prop(path, propertyValue, opt); + + if (!id) this.trigger('transition:end', this, path); + + }.bind(this); + + var initiator = function(callback) { + + this.stopTransitions(path); + + interpolatingFunction = opt.valueFunction(joint.util.getByPath(this.attributes, path, delim), value); + + this._transitionIds[path] = joint.util.nextFrame(callback); + + this.trigger('transition:start', this, path); + + }.bind(this); + + return setTimeout(initiator, opt.delay, setter); + }, + + getTransitions: function() { + + return Object.keys(this._transitionIds); + }, + + stopTransitions: function(path, delim) { + + delim = delim || '/'; + + var pathArray = path && path.split(delim); + + Object.keys(this._transitionIds).filter(pathArray && function(key) { + + return joint.util.isEqual(pathArray, key.split(delim).slice(0, pathArray.length)); + + }).forEach(function(key) { + + joint.util.cancelFrame(this._transitionIds[key]); + + delete this._transitionIds[key]; + + this.trigger('transition:end', this, key); + + }, this); + + return this; + }, + + // A shorcut making it easy to create constructs like the following: + // `var el = (new joint.shapes.basic.Rect).addTo(graph)`. + addTo: function(graph, opt) { + + graph.addCell(this, opt); + return this; + }, + + // A shortcut for an equivalent call: `paper.findViewByModel(cell)` + // making it easy to create constructs like the following: + // `cell.findView(paper).highlight()` + findView: function(paper) { + + return paper.findViewByModel(this); + }, + + isElement: function() { + + return false; + }, + + isLink: function() { + + return false; + }, + + startBatch: function(name, opt) { + + if (this.graph) { this.graph.startBatch(name, joint.util.assign({}, opt, { cell: this })); } + return this; + }, + + stopBatch: function(name, opt) { + + if (this.graph) { this.graph.stopBatch(name, joint.util.assign({}, opt, { cell: this })); } + return this; + } + +}, { + + getAttributeDefinition: function(attrName) { + + var defNS = this.attributes; + var globalDefNS = joint.dia.attributes; + return (defNS && defNS[attrName]) || globalDefNS[attrName]; + }, + + define: function(type, defaults, protoProps, staticProps) { + + protoProps = joint.util.assign({ + defaults: joint.util.defaultsDeep({ type: type }, defaults, this.prototype.defaults) + }, protoProps); + + var Cell = this.extend(protoProps, staticProps); + joint.util.setByPath(joint.shapes, type, Cell, '.'); + return Cell; + } +}); + +// joint.dia.CellView base view and controller. +// -------------------------------------------- + +// This is the base view and controller for `joint.dia.ElementView` and `joint.dia.LinkView`. + +joint.dia.CellView = joint.mvc.View.extend({ + + tagName: 'g', + + svgElement: true, + + selector: 'root', + + className: function() { + + var classNames = ['cell']; + var type = this.model.get('type'); + + if (type) { + + type.toLowerCase().split('.').forEach(function(value, index, list) { + classNames.push('type-' + list.slice(0, index + 1).join('-')); + }); + } + + return classNames.join(' '); + }, + + attributes: function() { + + return { 'model-id': this.model.id }; + }, + + constructor: function(options) { + + // Make sure a global unique id is assigned to this view. Store this id also to the properties object. + // The global unique id makes sure that the same view can be rendered on e.g. different machines and + // still be associated to the same object among all those clients. This is necessary for real-time + // collaboration mechanism. + options.id = options.id || joint.util.guid(this); + + joint.mvc.View.call(this, options); + }, + + init: function() { + + joint.util.bindAll(this, 'remove', 'update'); + + // Store reference to this to the <g> DOM element so that the view is accessible through the DOM tree. + this.$el.data('view', this); + + // Add the cell's type to the view's element as a data attribute. + this.$el.attr('data-type', this.model.get('type')); + + this.listenTo(this.model, 'change:attrs', this.onChangeAttrs); + }, + + onChangeAttrs: function(cell, attrs, opt) { + + if (opt.dirty) { + + // dirty flag could be set when a model attribute was removed and it needs to be cleared + // also from the DOM element. See cell.removeAttr(). + return this.render(); + } + + return this.update(cell, attrs, opt); + }, + + // Return `true` if cell link is allowed to perform a certain UI `feature`. + // Example: `can('vertexMove')`, `can('labelMove')`. + can: function(feature) { + + var interactive = joint.util.isFunction(this.options.interactive) + ? this.options.interactive(this) + : this.options.interactive; + + return (joint.util.isObject(interactive) && interactive[feature] !== false) || + (joint.util.isBoolean(interactive) && interactive !== false); + }, + + findBySelector: function(selector, root, selectors) { + + root || (root = this.el); + selectors || (selectors = this.selectors); + + // These are either descendants of `this.$el` of `this.$el` itself. + // `.` is a special selector used to select the wrapping `<g>` element. + if (!selector || selector === '.') return [root]; + if (selectors && selectors[selector]) return [selectors[selector]]; + // Maintaining backwards compatibility + // e.g. `circle:first` would fail with querySelector() call + return $(root).find(selector).toArray(); + }, + + notify: function(eventName) { + + if (this.paper) { + + var args = Array.prototype.slice.call(arguments, 1); + + // Trigger the event on both the element itself and also on the paper. + this.trigger.apply(this, [eventName].concat(args)); + + // Paper event handlers receive the view object as the first argument. + this.paper.trigger.apply(this.paper, [eventName, this].concat(args)); + } + }, + + // ** Deprecated ** + getStrokeBBox: function(el) { + // Return a bounding box rectangle that takes into account stroke. + // Note that this is a naive and ad-hoc implementation that does not + // works only in certain cases and should be replaced as soon as browsers will + // start supporting the getStrokeBBox() SVG method. + // @TODO any better solution is very welcome! + + var isMagnet = !!el; + + el = el || this.el; + var bbox = V(el).getBBox({ target: this.paper.viewport }); + var strokeWidth; + if (isMagnet) { + + strokeWidth = V(el).attr('stroke-width'); + + } else { + + strokeWidth = this.model.attr('rect/stroke-width') || this.model.attr('circle/stroke-width') || this.model.attr('ellipse/stroke-width') || this.model.attr('path/stroke-width'); + } + + strokeWidth = parseFloat(strokeWidth) || 0; + + return g.rect(bbox).moveAndExpand({ x: -strokeWidth / 2, y: -strokeWidth / 2, width: strokeWidth, height: strokeWidth }); + }, + + getBBox: function() { + + return this.vel.getBBox({ target: this.paper.svg }); + }, + + highlight: function(el, opt) { + + el = !el ? this.el : this.$(el)[0] || this.el; + + // set partial flag if the highlighted element is not the entire view. + opt = opt || {}; + opt.partial = (el !== this.el); + + this.notify('cell:highlight', el, opt); + return this; + }, + + unhighlight: function(el, opt) { + + el = !el ? this.el : this.$(el)[0] || this.el; + + opt = opt || {}; + opt.partial = el != this.el; + + this.notify('cell:unhighlight', el, opt); + return this; + }, + + // Find the closest element that has the `magnet` attribute set to `true`. If there was not such + // an element found, return the root element of the cell view. + findMagnet: function(el) { + + var $el = this.$(el); + var $rootEl = this.$el; + + if ($el.length === 0) { + $el = $rootEl; + } + + do { + + var magnet = $el.attr('magnet'); + if ((magnet || $el.is($rootEl)) && magnet !== 'false') { + return $el[0]; + } + + $el = $el.parent(); + + } while ($el.length > 0); + + // If the overall cell has set `magnet === false`, then return `undefined` to + // announce there is no magnet found for this cell. + // This is especially useful to set on cells that have 'ports'. In this case, + // only the ports have set `magnet === true` and the overall element has `magnet === false`. + return undefined; + }, + + // Construct a unique selector for the `el` element within this view. + // `prevSelector` is being collected through the recursive call. + // No value for `prevSelector` is expected when using this method. + getSelector: function(el, prevSelector) { + + if (el === this.el) { + return prevSelector; + } + + var selector; + + if (el) { + + var nthChild = V(el).index() + 1; + selector = el.tagName + ':nth-child(' + nthChild + ')'; + + if (prevSelector) { + selector += ' > ' + prevSelector; + } + + selector = this.getSelector(el.parentNode, selector); + } + + return selector; + }, + + getLinkEnd: function(magnet, x, y, link, endType) { + + var model = this.model; + var id = model.id; + var port = this.findAttribute('port', magnet); + // Find a unique `selector` of the element under pointer that is a magnet. + var selector = magnet.getAttribute('joint-selector'); + + var end = { id: id }; + if (selector != null) end.magnet = selector; + if (port != null) { + end.port = port; + if (!model.hasPort(port) && !selector) { + // port created via the `port` attribute (not API) + end.selector = this.getSelector(magnet); + } + } else if (selector == null && this.el !== magnet) { + end.selector = this.getSelector(magnet); + } + + var paper = this.paper; + var connectionStrategy = paper.options.connectionStrategy; + if (typeof connectionStrategy === 'function') { + var strategy = connectionStrategy.call(paper, end, this, magnet, new g.Point(x, y), link, endType); + if (strategy) end = strategy; + } + + return end; + }, + + getMagnetFromLinkEnd: function(end) { + + var root = this.el; + var port = end.port; + var selector = end.magnet; + var magnet; + if (port != null && this.model.hasPort(port)) { + magnet = this.findPortNode(port, selector) || root; + } else { + if (!selector) selector = end.selector; + if (!selector && port != null) { + // link end has only `id` and `port` property referencing + // a port created via the `port` attribute (not API). + selector = '[port="' + port + '"]'; + } + magnet = this.findBySelector(selector, root, this.selectors)[0]; + } + + return magnet; + }, + + findAttribute: function(attributeName, node) { + + if (!node) return null; + + var attributeValue = node.getAttribute(attributeName); + if (attributeValue === null) { + if (node === this.el) return null; + var currentNode = node.parentNode; + while (currentNode && currentNode !== this.el && currentNode.nodeType === 1) { + attributeValue = currentNode.getAttribute(attributeName) + if (attributeValue !== null) break; + currentNode = currentNode.parentNode; + } + } + return attributeValue; + }, + + getAttributeDefinition: function(attrName) { + + return this.model.constructor.getAttributeDefinition(attrName); + }, + + setNodeAttributes: function(node, attrs) { + + if (!joint.util.isEmpty(attrs)) { + if (node instanceof SVGElement) { + V(node).attr(attrs); + } else { + $(node).attr(attrs); + } + } + }, + + processNodeAttributes: function(node, attrs) { + + var attrName, attrVal, def, i, n; + var normalAttrs, setAttrs, positionAttrs, offsetAttrs; + var relatives = []; + // divide the attributes between normal and special + for (attrName in attrs) { + if (!attrs.hasOwnProperty(attrName)) continue; + attrVal = attrs[attrName]; + def = this.getAttributeDefinition(attrName); + if (def && (!joint.util.isFunction(def.qualify) || def.qualify.call(this, attrVal, node, attrs))) { + if (joint.util.isString(def.set)) { + normalAttrs || (normalAttrs = {}); + normalAttrs[def.set] = attrVal; + } + if (attrVal !== null) { + relatives.push(attrName, def); + } + } else { + normalAttrs || (normalAttrs = {}); + normalAttrs[joint.util.toKebabCase(attrName)] = attrVal; + } + } + + // handle the rest of attributes via related method + // from the special attributes namespace. + for (i = 0, n = relatives.length; i < n; i+=2) { + attrName = relatives[i]; + def = relatives[i+1]; + attrVal = attrs[attrName]; + if (joint.util.isFunction(def.set)) { + setAttrs || (setAttrs = {}); + setAttrs[attrName] = attrVal; + } + if (joint.util.isFunction(def.position)) { + positionAttrs || (positionAttrs = {}); + positionAttrs[attrName] = attrVal; + } + if (joint.util.isFunction(def.offset)) { + offsetAttrs || (offsetAttrs = {}); + offsetAttrs[attrName] = attrVal; + } + } + + return { + raw: attrs, + normal: normalAttrs, + set: setAttrs, + position: positionAttrs, + offset: offsetAttrs + }; + }, + + updateRelativeAttributes: function(node, attrs, refBBox, opt) { + + opt || (opt = {}); + + var attrName, attrVal, def; + var rawAttrs = attrs.raw || {}; + var nodeAttrs = attrs.normal || {}; + var setAttrs = attrs.set; + var positionAttrs = attrs.position; + var offsetAttrs = attrs.offset; + + for (attrName in setAttrs) { + attrVal = setAttrs[attrName]; + def = this.getAttributeDefinition(attrName); + // SET - set function should return attributes to be set on the node, + // which will affect the node dimensions based on the reference bounding + // box. e.g. `width`, `height`, `d`, `rx`, `ry`, `points + var setResult = def.set.call(this, attrVal, refBBox.clone(), node, rawAttrs); + if (joint.util.isObject(setResult)) { + joint.util.assign(nodeAttrs, setResult); + } else if (setResult !== undefined) { + nodeAttrs[attrName] = setResult; + } + } + + if (node instanceof HTMLElement) { + // TODO: setting the `transform` attribute on HTMLElements + // via `node.style.transform = 'matrix(...)';` would introduce + // a breaking change (e.g. basic.TextBlock). + this.setNodeAttributes(node, nodeAttrs); + return; + } + + // The final translation of the subelement. + var nodeTransform = nodeAttrs.transform; + var nodeMatrix = V.transformStringToMatrix(nodeTransform); + var nodePosition = g.Point(nodeMatrix.e, nodeMatrix.f); + if (nodeTransform) { + nodeAttrs = joint.util.omit(nodeAttrs, 'transform'); + nodeMatrix.e = nodeMatrix.f = 0; + } + + // Calculate node scale determined by the scalable group + // only if later needed. + var sx, sy, translation; + if (positionAttrs || offsetAttrs) { + var nodeScale = this.getNodeScale(node, opt.scalableNode); + sx = nodeScale.sx; + sy = nodeScale.sy; + } + + var positioned = false; + for (attrName in positionAttrs) { + attrVal = positionAttrs[attrName]; + def = this.getAttributeDefinition(attrName); + // POSITION - position function should return a point from the + // reference bounding box. The default position of the node is x:0, y:0 of + // the reference bounding box or could be further specify by some + // SVG attributes e.g. `x`, `y` + translation = def.position.call(this, attrVal, refBBox.clone(), node, rawAttrs); + if (translation) { + nodePosition.offset(g.Point(translation).scale(sx, sy)); + positioned || (positioned = true); + } + } + + // The node bounding box could depend on the `size` set from the previous loop. + // Here we know, that all the size attributes have been already set. + this.setNodeAttributes(node, nodeAttrs); + + var offseted = false; + if (offsetAttrs) { + // Check if the node is visible + var nodeClientRect = node.getBoundingClientRect(); + if (nodeClientRect.width > 0 && nodeClientRect.height > 0) { + var nodeBBox = V.transformRect(node.getBBox(), nodeMatrix).scale(1 / sx, 1 / sy); + for (attrName in offsetAttrs) { + attrVal = offsetAttrs[attrName]; + def = this.getAttributeDefinition(attrName); + // OFFSET - offset function should return a point from the element + // bounding box. The default offset point is x:0, y:0 (origin) or could be further + // specify with some SVG attributes e.g. `text-anchor`, `cx`, `cy` + translation = def.offset.call(this, attrVal, nodeBBox, node, rawAttrs); + if (translation) { + nodePosition.offset(g.Point(translation).scale(sx, sy)); + offseted || (offseted = true); + } + } + } + } + + // Do not touch node's transform attribute if there is no transformation applied. + if (nodeTransform !== undefined || positioned || offseted) { + // Round the coordinates to 1 decimal point. + nodePosition.round(1); + nodeMatrix.e = nodePosition.x; + nodeMatrix.f = nodePosition.y; + node.setAttribute('transform', V.matrixToTransformString(nodeMatrix)); + // TODO: store nodeMatrix metrics? + } + }, + + getNodeScale: function(node, scalableNode) { + + // Check if the node is a descendant of the scalable group. + var sx, sy; + if (scalableNode && scalableNode.contains(node)) { + var scale = scalableNode.scale(); + sx = 1 / scale.sx; + sy = 1 / scale.sy; + } else { + sx = 1; + sy = 1; + } + + return { sx: sx, sy: sy }; + }, + + findNodesAttributes: function(attrs, root, selectorCache, selectors) { + + // TODO: merge attributes in order defined by `index` property + + // most browsers sort elements in attrs by order of addition + // which is useful but not required + + // link.updateLabels() relies on that assumption for merging label attrs over default label attrs + + var nodesAttrs = {}; + + for (var selector in attrs) { + if (!attrs.hasOwnProperty(selector)) continue; + var selected = selectorCache[selector] = this.findBySelector(selector, root, selectors); + for (var i = 0, n = selected.length; i < n; i++) { + var node = selected[i]; + var nodeId = V.ensureId(node); + var nodeAttrs = attrs[selector]; + var prevNodeAttrs = nodesAttrs[nodeId]; + if (prevNodeAttrs) { + if (!prevNodeAttrs.merged) { + prevNodeAttrs.merged = true; + // if prevNode attrs is `null`, replace with `{}` + prevNodeAttrs.attributes = joint.util.cloneDeep(prevNodeAttrs.attributes) || {}; + } + // if prevNode attrs not set (or `null` or`{}`), use node attrs + // if node attrs not set (or `null` or `{}`), use prevNode attrs + joint.util.merge(prevNodeAttrs.attributes, nodeAttrs); + } else { + nodesAttrs[nodeId] = { + attributes: nodeAttrs, + node: node, + merged: false + }; + } + } + } + + return nodesAttrs; + }, + + // Default is to process the `model.attributes.attrs` object and set attributes on subelements based on the selectors, + // unless `attrs` parameter was passed. + updateDOMSubtreeAttributes: function(rootNode, attrs, opt) { + + opt || (opt = {}); + opt.rootBBox || (opt.rootBBox = g.Rect()); + opt.selectors || (opt.selectors = this.selectors); // selector collection to use + + // Cache table for query results and bounding box calculation. + // Note that `selectorCache` needs to be invalidated for all + // `updateAttributes` calls, as the selectors might pointing + // to nodes designated by an attribute or elements dynamically + // created. + var selectorCache = {}; + var bboxCache = {}; + var relativeItems = []; + var item, node, nodeAttrs, nodeData, processedAttrs; + + var roAttrs = opt.roAttributes; + var nodesAttrs = this.findNodesAttributes(roAttrs || attrs, rootNode, selectorCache, opt.selectors); + // `nodesAttrs` are different from all attributes, when + // rendering only attributes sent to this method. + var nodesAllAttrs = (roAttrs) + ? nodesAllAttrs = this.findNodesAttributes(attrs, rootNode, selectorCache, opt.selectors) + : nodesAttrs; + + for (var nodeId in nodesAttrs) { + nodeData = nodesAttrs[nodeId]; + nodeAttrs = nodeData.attributes; + node = nodeData.node; + processedAttrs = this.processNodeAttributes(node, nodeAttrs); + + if (!processedAttrs.set && !processedAttrs.position && !processedAttrs.offset) { + // Set all the normal attributes right on the SVG/HTML element. + this.setNodeAttributes(node, processedAttrs.normal); + + } else { + + var nodeAllAttrs = nodesAllAttrs[nodeId] && nodesAllAttrs[nodeId].attributes; + var refSelector = (nodeAllAttrs && (nodeAttrs.ref === undefined)) + ? nodeAllAttrs.ref + : nodeAttrs.ref; + + var refNode; + if (refSelector) { + refNode = (selectorCache[refSelector] || this.findBySelector(refSelector, rootNode, opt.selectors))[0]; + if (!refNode) { + throw new Error('dia.ElementView: "' + refSelector + '" reference does not exist.'); + } + } else { + refNode = null; + } + + item = { + node: node, + refNode: refNode, + processedAttributes: processedAttrs, + allAttributes: nodeAllAttrs + }; + + // If an element in the list is positioned relative to this one, then + // we want to insert this one before it in the list. + var itemIndex = relativeItems.findIndex(function(item) { + return item.refNode === node; + }); + + if (itemIndex > -1) { + relativeItems.splice(itemIndex, 0, item); + } else { + relativeItems.push(item); + } + } + } + + for (var i = 0, n = relativeItems.length; i < n; i++) { + item = relativeItems[i]; + node = item.node; + refNode = item.refNode; + + // Find the reference element bounding box. If no reference was provided, we + // use the optional bounding box. + var refNodeId = refNode ? V.ensureId(refNode) : ''; + var refBBox = bboxCache[refNodeId]; + if (!refBBox) { + // Get the bounding box of the reference element relative to the `rotatable` `<g>` (without rotation) + // or to the root `<g>` element if no rotatable group present if reference node present. + // Uses the bounding box provided. + refBBox = bboxCache[refNodeId] = (refNode) + ? V(refNode).getBBox({ target: (opt.rotatableNode || rootNode) }) + : opt.rootBBox; + } + + if (roAttrs) { + // if there was a special attribute affecting the position amongst passed-in attributes + // we have to merge it with the rest of the element's attributes as they are necessary + // to update the position relatively (i.e `ref-x` && 'ref-dx') + processedAttrs = this.processNodeAttributes(node, item.allAttributes); + this.mergeProcessedAttributes(processedAttrs, item.processedAttributes); + + } else { + processedAttrs = item.processedAttributes; + } + + this.updateRelativeAttributes(node, processedAttrs, refBBox, opt); + } + }, + + mergeProcessedAttributes: function(processedAttrs, roProcessedAttrs) { + + processedAttrs.set || (processedAttrs.set = {}); + processedAttrs.position || (processedAttrs.position = {}); + processedAttrs.offset || (processedAttrs.offset = {}); + + joint.util.assign(processedAttrs.set, roProcessedAttrs.set); + joint.util.assign(processedAttrs.position, roProcessedAttrs.position); + joint.util.assign(processedAttrs.offset, roProcessedAttrs.offset); + + // Handle also the special transform property. + var transform = processedAttrs.normal && processedAttrs.normal.transform; + if (transform !== undefined && roProcessedAttrs.normal) { + roProcessedAttrs.normal.transform = transform; + } + processedAttrs.normal = roProcessedAttrs.normal; + }, + + onRemove: function() { + this.removeTools(); + }, + + _toolsView: null, + + hasTools: function(name) { + var toolsView = this._toolsView; + if (!toolsView) return false; + if (!name) return true; + return (toolsView.getName() === name); + }, + + addTools: function(toolsView) { + + this.removeTools(); + + if (toolsView instanceof joint.dia.ToolsView) { + this._toolsView = toolsView; + toolsView.configure({ relatedView: this }); + toolsView.listenTo(this.paper, 'tools:event', this.onToolEvent.bind(this)); + toolsView.mount(); + } + return this; + }, + + updateTools: function(opt) { + + var toolsView = this._toolsView; + if (toolsView) toolsView.update(opt); + return this; + }, + + removeTools: function() { + + var toolsView = this._toolsView; + if (toolsView) { + toolsView.remove(); + this._toolsView = null; + } + return this; + }, + + hideTools: function() { + + var toolsView = this._toolsView; + if (toolsView) toolsView.hide(); + return this; + }, + + showTools: function() { + + var toolsView = this._toolsView; + if (toolsView) toolsView.show(); + return this; + }, + + onToolEvent: function(event) { + switch (event) { + case 'remove': + this.removeTools(); + break; + case 'hide': + this.hideTools(); + break; + case 'show': + this.showTools(); + break; + } + }, + + // Interaction. The controller part. + // --------------------------------- + + // Interaction is handled by the paper and delegated to the view in interest. + // `x` & `y` parameters passed to these functions represent the coordinates already snapped to the paper grid. + // If necessary, real coordinates can be obtained from the `evt` event object. + + // These functions are supposed to be overriden by the views that inherit from `joint.dia.Cell`, + // i.e. `joint.dia.Element` and `joint.dia.Link`. + + pointerdblclick: function(evt, x, y) { + + this.notify('cell:pointerdblclick', evt, x, y); + }, + + pointerclick: function(evt, x, y) { + + this.notify('cell:pointerclick', evt, x, y); + }, + + contextmenu: function(evt, x, y) { + + this.notify('cell:contextmenu', evt, x, y); + }, + + pointerdown: function(evt, x, y) { + + if (this.model.graph) { + this.model.startBatch('pointer'); + this._graph = this.model.graph; + } + + this.notify('cell:pointerdown', evt, x, y); + }, + + pointermove: function(evt, x, y) { + + this.notify('cell:pointermove', evt, x, y); + }, + + pointerup: function(evt, x, y) { + + this.notify('cell:pointerup', evt, x, y); + + if (this._graph) { + // we don't want to trigger event on model as model doesn't + // need to be member of collection anymore (remove) + this._graph.stopBatch('pointer', { cell: this.model }); + delete this._graph; + } + }, + + mouseover: function(evt) { + + this.notify('cell:mouseover', evt); + }, + + mouseout: function(evt) { + + this.notify('cell:mouseout', evt); + }, + + mouseenter: function(evt) { + + this.notify('cell:mouseenter', evt); + }, + + mouseleave: function(evt) { + + this.notify('cell:mouseleave', evt); + }, + + mousewheel: function(evt, x, y, delta) { + + this.notify('cell:mousewheel', evt, x, y, delta); + }, + + onevent: function(evt, eventName, x, y) { + + this.notify(eventName, evt, x, y); + }, + + onmagnet: function() { + + // noop + }, + + setInteractivity: function(value) { + + this.options.interactive = value; + } +}, { + + dispatchToolsEvent: function(paper, event) { + if ((typeof event === 'string') && (paper instanceof joint.dia.Paper)) { + paper.trigger('tools:event', event); + } + } +}); + + +// joint.dia.Element base model. +// ----------------------------- + +joint.dia.Element = joint.dia.Cell.extend({ + + defaults: { + position: { x: 0, y: 0 }, + size: { width: 1, height: 1 }, + angle: 0 + }, + + initialize: function() { + + this._initializePorts(); + joint.dia.Cell.prototype.initialize.apply(this, arguments); + }, + + /** + * @abstract + */ + _initializePorts: function() { + // implemented in ports.js + }, + + isElement: function() { + + return true; + }, + + position: function(x, y, opt) { + + var isSetter = joint.util.isNumber(y); + + opt = (isSetter ? opt : x) || {}; + + // option `parentRelative` for setting the position relative to the element's parent. + if (opt.parentRelative) { + + // Getting the parent's position requires the collection. + // Cell.parent() holds cell id only. + if (!this.graph) throw new Error('Element must be part of a graph.'); + + var parent = this.getParentCell(); + var parentPosition = parent && !parent.isLink() + ? parent.get('position') + : { x: 0, y: 0 }; + } + + if (isSetter) { + + if (opt.parentRelative) { + x += parentPosition.x; + y += parentPosition.y; + } + + if (opt.deep) { + var currentPosition = this.get('position'); + this.translate(x - currentPosition.x, y - currentPosition.y, opt); + } else { + this.set('position', { x: x, y: y }, opt); + } + + return this; + + } else { // Getter returns a geometry point. + + var elementPosition = g.point(this.get('position')); + + return opt.parentRelative + ? elementPosition.difference(parentPosition) + : elementPosition; + } + }, + + translate: function(tx, ty, opt) { + + tx = tx || 0; + ty = ty || 0; + + if (tx === 0 && ty === 0) { + // Like nothing has happened. + return this; + } + + opt = opt || {}; + // Pass the initiator of the translation. + opt.translateBy = opt.translateBy || this.id; + + var position = this.get('position') || { x: 0, y: 0 }; + + if (opt.restrictedArea && opt.translateBy === this.id) { + + // We are restricting the translation for the element itself only. We get + // the bounding box of the element including all its embeds. + // All embeds have to be translated the exact same way as the element. + var bbox = this.getBBox({ deep: true }); + var ra = opt.restrictedArea; + //- - - - - - - - - - - - -> ra.x + ra.width + // - - - -> position.x | + // -> bbox.x + // ▓▓▓▓▓▓▓ | + // ░░░░░░░▓▓▓▓▓▓▓ + // ░░░░░░░░░ | + // ▓▓▓▓▓▓▓▓░░░░░░░ + // ▓▓▓▓▓▓▓▓ | + // <-dx-> | restricted area right border + // <-width-> | ░ translated element + // <- - bbox.width - -> ▓ embedded element + var dx = position.x - bbox.x; + var dy = position.y - bbox.y; + // Find the maximal/minimal coordinates that the element can be translated + // while complies the restrictions. + var x = Math.max(ra.x + dx, Math.min(ra.x + ra.width + dx - bbox.width, position.x + tx)); + var y = Math.max(ra.y + dy, Math.min(ra.y + ra.height + dy - bbox.height, position.y + ty)); + // recalculate the translation taking the resctrictions into account. + tx = x - position.x; + ty = y - position.y; + } + + var translatedPosition = { + x: position.x + tx, + y: position.y + ty + }; + + // To find out by how much an element was translated in event 'change:position' handlers. + opt.tx = tx; + opt.ty = ty; + + if (opt.transition) { + + if (!joint.util.isObject(opt.transition)) opt.transition = {}; + + this.transition('position', translatedPosition, joint.util.assign({}, opt.transition, { + valueFunction: joint.util.interpolate.object + })); + + } else { + + this.set('position', translatedPosition, opt); + } + + // Recursively call `translate()` on all the embeds cells. + joint.util.invoke(this.getEmbeddedCells(), 'translate', tx, ty, opt); + + return this; + }, + + size: function(width, height, opt) { + + var currentSize = this.get('size'); + // Getter + // () signature + if (width === undefined) { + return { + width: currentSize.width, + height: currentSize.height + }; + } + // Setter + // (size, opt) signature + if (joint.util.isObject(width)) { + opt = height; + height = joint.util.isNumber(width.height) ? width.height : currentSize.height; + width = joint.util.isNumber(width.width) ? width.width : currentSize.width; + } + + return this.resize(width, height, opt); + }, + + resize: function(width, height, opt) { + + opt = opt || {}; + + this.startBatch('resize', opt); + + if (opt.direction) { + + var currentSize = this.get('size'); + + switch (opt.direction) { + + case 'left': + case 'right': + // Don't change height when resizing horizontally. + height = currentSize.height; + break; + + case 'top': + case 'bottom': + // Don't change width when resizing vertically. + width = currentSize.width; + break; + } + + // Get the angle and clamp its value between 0 and 360 degrees. + var angle = g.normalizeAngle(this.get('angle') || 0); + + var quadrant = { + 'top-right': 0, + 'right': 0, + 'top-left': 1, + 'top': 1, + 'bottom-left': 2, + 'left': 2, + 'bottom-right': 3, + 'bottom': 3 + }[opt.direction]; + + if (opt.absolute) { + + // We are taking the element's rotation into account + quadrant += Math.floor((angle + 45) / 90); + quadrant %= 4; + } + + // This is a rectangle in size of the unrotated element. + var bbox = this.getBBox(); + + // Pick the corner point on the element, which meant to stay on its place before and + // after the rotation. + var fixedPoint = bbox[['bottomLeft', 'corner', 'topRight', 'origin'][quadrant]](); + + // Find an image of the previous indent point. This is the position, where is the + // point actually located on the screen. + var imageFixedPoint = g.point(fixedPoint).rotate(bbox.center(), -angle); + + // Every point on the element rotates around a circle with the centre of rotation + // in the middle of the element while the whole element is being rotated. That means + // that the distance from a point in the corner of the element (supposed its always rect) to + // the center of the element doesn't change during the rotation and therefore it equals + // to a distance on unrotated element. + // We can find the distance as DISTANCE = (ELEMENTWIDTH/2)^2 + (ELEMENTHEIGHT/2)^2)^0.5. + var radius = Math.sqrt((width * width) + (height * height)) / 2; + + // Now we are looking for an angle between x-axis and the line starting at image of fixed point + // and ending at the center of the element. We call this angle `alpha`. + + // The image of a fixed point is located in n-th quadrant. For each quadrant passed + // going anti-clockwise we have to add 90 degrees. Note that the first quadrant has index 0. + // + // 3 | 2 + // --c-- Quadrant positions around the element's center `c` + // 0 | 1 + // + var alpha = quadrant * Math.PI / 2; + + // Add an angle between the beginning of the current quadrant (line parallel with x-axis or y-axis + // going through the center of the element) and line crossing the indent of the fixed point and the center + // of the element. This is the angle we need but on the unrotated element. + alpha += Math.atan(quadrant % 2 == 0 ? height / width : width / height); + + // Lastly we have to deduct the original angle the element was rotated by and that's it. + alpha -= g.toRad(angle); + + // With this angle and distance we can easily calculate the centre of the unrotated element. + // Note that fromPolar constructor accepts an angle in radians. + var center = g.point.fromPolar(radius, alpha, imageFixedPoint); + + // The top left corner on the unrotated element has to be half a width on the left + // and half a height to the top from the center. This will be the origin of rectangle + // we were looking for. + var origin = g.point(center).offset(width / -2, height / -2); + + // Resize the element (before re-positioning it). + this.set('size', { width: width, height: height }, opt); + + // Finally, re-position the element. + this.position(origin.x, origin.y, opt); + + } else { + + // Resize the element. + this.set('size', { width: width, height: height }, opt); + } + + this.stopBatch('resize', opt); + + return this; + }, + + scale: function(sx, sy, origin, opt) { + + var scaledBBox = this.getBBox().scale(sx, sy, origin); + this.startBatch('scale', opt); + this.position(scaledBBox.x, scaledBBox.y, opt); + this.resize(scaledBBox.width, scaledBBox.height, opt); + this.stopBatch('scale'); + return this; + }, + + fitEmbeds: function(opt) { + + opt = opt || {}; + + // Getting the children's size and position requires the collection. + // Cell.get('embdes') helds an array of cell ids only. + if (!this.graph) throw new Error('Element must be part of a graph.'); + + var embeddedCells = this.getEmbeddedCells(); + + if (embeddedCells.length > 0) { + + this.startBatch('fit-embeds', opt); + + if (opt.deep) { + // Recursively apply fitEmbeds on all embeds first. + joint.util.invoke(embeddedCells, 'fitEmbeds', opt); + } + + // Compute cell's size and position based on the children bbox + // and given padding. + var bbox = this.graph.getCellsBBox(embeddedCells); + var padding = joint.util.normalizeSides(opt.padding); + + // Apply padding computed above to the bbox. + bbox.moveAndExpand({ + x: -padding.left, + y: -padding.top, + width: padding.right + padding.left, + height: padding.bottom + padding.top + }); + + // Set new element dimensions finally. + this.set({ + position: { x: bbox.x, y: bbox.y }, + size: { width: bbox.width, height: bbox.height } + }, opt); + + this.stopBatch('fit-embeds'); + } + + return this; + }, + + // Rotate element by `angle` degrees, optionally around `origin` point. + // If `origin` is not provided, it is considered to be the center of the element. + // If `absolute` is `true`, the `angle` is considered is abslute, i.e. it is not + // the difference from the previous angle. + rotate: function(angle, absolute, origin, opt) { + + if (origin) { + + var center = this.getBBox().center(); + var size = this.get('size'); + var position = this.get('position'); + center.rotate(origin, this.get('angle') - angle); + var dx = center.x - size.width / 2 - position.x; + var dy = center.y - size.height / 2 - position.y; + this.startBatch('rotate', { angle: angle, absolute: absolute, origin: origin }); + this.position(position.x + dx, position.y + dy, opt); + this.rotate(angle, absolute, null, opt); + this.stopBatch('rotate'); + + } else { + + this.set('angle', absolute ? angle : (this.get('angle') + angle) % 360, opt); + } + + return this; + }, + + angle: function() { + return g.normalizeAngle(this.get('angle') || 0); + }, + + getBBox: function(opt) { + + opt = opt || {}; + + if (opt.deep && this.graph) { + + // Get all the embedded elements using breadth first algorithm, + // that doesn't use recursion. + var elements = this.getEmbeddedCells({ deep: true, breadthFirst: true }); + // Add the model itself. + elements.push(this); + + return this.graph.getCellsBBox(elements); + } + + var position = this.get('position'); + var size = this.get('size'); + + return new g.Rect(position.x, position.y, size.width, size.height); + } +}); + +// joint.dia.Element base view and controller. +// ------------------------------------------- + +joint.dia.ElementView = joint.dia.CellView.extend({ + + /** + * @abstract + */ + _removePorts: function() { + // implemented in ports.js + }, + + /** + * + * @abstract + */ + _renderPorts: function() { + // implemented in ports.js + }, + + className: function() { + + var classNames = joint.dia.CellView.prototype.className.apply(this).split(' '); + + classNames.push('element'); + + return classNames.join(' '); + }, + + metrics: null, + + initialize: function() { + + joint.dia.CellView.prototype.initialize.apply(this, arguments); + + var model = this.model; + + this.listenTo(model, 'change:position', this.translate); + this.listenTo(model, 'change:size', this.resize); + this.listenTo(model, 'change:angle', this.rotate); + this.listenTo(model, 'change:markup', this.render); + + this._initializePorts(); + + this.metrics = {}; + }, + + /** + * @abstract + */ + _initializePorts: function() { + + }, + + update: function(cell, renderingOnlyAttrs) { + + this.metrics = {}; + + this._removePorts(); + + var model = this.model; + var modelAttrs = model.attr(); + this.updateDOMSubtreeAttributes(this.el, modelAttrs, { + rootBBox: new g.Rect(model.size()), + selectors: this.selectors, + scalableNode: this.scalableNode, + rotatableNode: this.rotatableNode, + // Use rendering only attributes if they differs from the model attributes + roAttributes: (renderingOnlyAttrs === modelAttrs) ? null : renderingOnlyAttrs + }); + + this._renderPorts(); + }, + + rotatableSelector: 'rotatable', + scalableSelector: 'scalable', + scalableNode: null, + rotatableNode: null, + + // `prototype.markup` is rendered by default. Set the `markup` attribute on the model if the + // default markup is not desirable. + renderMarkup: function() { + + var element = this.model; + var markup = element.get('markup') || element.markup; + if (!markup) throw new Error('dia.ElementView: markup required'); + if (Array.isArray(markup)) return this.renderJSONMarkup(markup); + if (typeof markup === 'string') return this.renderStringMarkup(markup); + throw new Error('dia.ElementView: invalid markup'); + }, + + renderJSONMarkup: function(markup) { + + var doc = joint.util.parseDOMJSON(markup); + // Selectors + var selectors = this.selectors = doc.selectors; + var rootSelector = this.selector; + if (selectors[rootSelector]) throw new Error('dia.ElementView: ambiguous root selector.'); + selectors[rootSelector] = this.el; + // Cache transformation groups + this.rotatableNode = V(selectors[this.rotatableSelector]) || null; + this.scalableNode = V(selectors[this.scalableSelector]) || null; + // Fragment + this.vel.append(doc.fragment); + }, + + renderStringMarkup: function(markup) { + + var vel = this.vel; + vel.append(V(markup)); + // Cache transformation groups + this.rotatableNode = vel.findOne('.rotatable'); + this.scalableNode = vel.findOne('.scalable'); + + var selectors = this.selectors = {}; + selectors[this.selector] = this.el; + }, + + render: function() { + + this.vel.empty(); + this.renderMarkup(); + if (this.scalableNode) { + // Double update is necessary for elements with the scalable group only + // Note the resize() triggers the other `update`. + this.update(); + } + this.resize(); + if (this.rotatableNode) { + // Translate transformation is applied on `this.el` while the rotation transformation + // on `this.rotatableNode` + this.rotate(); + this.translate(); + return this; + } + this.updateTransformation(); + return this; + }, + + resize: function() { + + if (this.scalableNode) return this.sgResize.apply(this, arguments); + if (this.model.attributes.angle) this.rotate(); + this.update(); + }, + + translate: function() { + + if (this.rotatableNode) return this.rgTranslate(); + this.updateTransformation(); + }, + + rotate: function() { + + if (this.rotatableNode) return this.rgRotate(); + this.updateTransformation(); + }, + + updateTransformation: function() { + + var transformation = this.getTranslateString(); + var rotateString = this.getRotateString(); + if (rotateString) transformation += ' ' + rotateString; + this.vel.attr('transform', transformation); + }, + + getTranslateString: function() { + + var position = this.model.attributes.position; + return 'translate(' + position.x + ',' + position.y + ')'; + }, + + getRotateString: function() { + var attributes = this.model.attributes; + var angle = attributes.angle; + if (!angle) return null; + var size = attributes.size; + return 'rotate(' + angle + ',' + (size.width / 2) + ',' + (size.height / 2) + ')'; + }, + + getBBox: function(opt) { + + var bbox; + if (opt && opt.useModelGeometry) { + var model = this.model; + bbox = model.getBBox().bbox(model.angle()); + } else { + bbox = this.getNodeBBox(this.el); + } + + return this.paper.localToPaperRect(bbox); + }, + + nodeCache: function(magnet) { + + var id = V.ensureId(magnet); + var metrics = this.metrics[id]; + if (!metrics) metrics = this.metrics[id] = {}; + return metrics; + }, + + getNodeData: function(magnet) { + + var metrics = this.nodeCache(magnet); + if (!metrics.data) metrics.data = {}; + return metrics.data; + }, + + getNodeBBox: function(magnet) { + + var rect = this.getNodeBoundingRect(magnet); + var magnetMatrix = this.getNodeMatrix(magnet); + var translateMatrix = this.getRootTranslateMatrix(); + var rotateMatrix = this.getRootRotateMatrix(); + return V.transformRect(rect, translateMatrix.multiply(rotateMatrix).multiply(magnetMatrix)); + }, + + getNodeBoundingRect: function(magnet) { + + var metrics = this.nodeCache(magnet); + if (metrics.boundingRect === undefined) metrics.boundingRect = V(magnet).getBBox(); + return new g.Rect(metrics.boundingRect); + }, + + getNodeUnrotatedBBox: function(magnet) { + + var rect = this.getNodeBoundingRect(magnet); + var magnetMatrix = this.getNodeMatrix(magnet); + var translateMatrix = this.getRootTranslateMatrix(); + return V.transformRect(rect, translateMatrix.multiply(magnetMatrix)); + }, + + getNodeShape: function(magnet) { + + var metrics = this.nodeCache(magnet); + if (metrics.geometryShape === undefined) metrics.geometryShape = V(magnet).toGeometryShape(); + return metrics.geometryShape.clone(); + }, + + getNodeMatrix: function(magnet) { + + var metrics = this.nodeCache(magnet); + if (metrics.magnetMatrix === undefined) { + var target = this.rotatableNode || this.el; + metrics.magnetMatrix = V(magnet).getTransformToElement(target); + } + return V.createSVGMatrix(metrics.magnetMatrix); + }, + + getRootTranslateMatrix: function() { + + var model = this.model; + var position = model.position(); + var mt = V.createSVGMatrix().translate(position.x, position.y); + return mt; + }, + + getRootRotateMatrix: function() { + + var mr = V.createSVGMatrix(); + var model = this.model; + var angle = model.angle(); + if (angle) { + var bbox = model.getBBox(); + var cx = bbox.width / 2; + var cy = bbox.height / 2; + mr = mr.translate(cx, cy).rotate(angle).translate(-cx, -cy); + } + return mr; + }, + + // Rotatable & Scalable Group + // always slower, kept mainly for backwards compatibility + + rgRotate: function() { + + this.rotatableNode.attr('transform', this.getRotateString()); + }, + + rgTranslate: function() { + + this.vel.attr('transform', this.getTranslateString()); + }, + + sgResize: function(cell, changed, opt) { + + var model = this.model; + var angle = model.get('angle') || 0; + var size = model.get('size') || { width: 1, height: 1 }; + var scalable = this.scalableNode; + + // Getting scalable group's bbox. + // Due to a bug in webkit's native SVG .getBBox implementation, the bbox of groups with path children includes the paths' control points. + // To work around the issue, we need to check whether there are any path elements inside the scalable group. + var recursive = false; + if (scalable.node.getElementsByTagName('path').length > 0) { + // If scalable has at least one descendant that is a path, we need to switch to recursive bbox calculation. + // If there are no path descendants, group bbox calculation works and so we can use the (faster) native function directly. + recursive = true; + } + var scalableBBox = scalable.getBBox({ recursive: recursive }); + + // Make sure `scalableBbox.width` and `scalableBbox.height` are not zero which can happen if the element does not have any content. By making + // the width/height 1, we prevent HTML errors of the type `scale(Infinity, Infinity)`. + var sx = (size.width / (scalableBBox.width || 1)); + var sy = (size.height / (scalableBBox.height || 1)); + scalable.attr('transform', 'scale(' + sx + ',' + sy + ')'); + + // Now the interesting part. The goal is to be able to store the object geometry via just `x`, `y`, `angle`, `width` and `height` + // Order of transformations is significant but we want to reconstruct the object always in the order: + // resize(), rotate(), translate() no matter of how the object was transformed. For that to work, + // we must adjust the `x` and `y` coordinates of the object whenever we resize it (because the origin of the + // rotation changes). The new `x` and `y` coordinates are computed by canceling the previous rotation + // around the center of the resized object (which is a different origin then the origin of the previous rotation) + // and getting the top-left corner of the resulting object. Then we clean up the rotation back to what it originally was. + + // Cancel the rotation but now around a different origin, which is the center of the scaled object. + var rotatable = this.rotatableNode; + var rotation = rotatable && rotatable.attr('transform'); + if (rotation && rotation !== null) { + + rotatable.attr('transform', rotation + ' rotate(' + (-angle) + ',' + (size.width / 2) + ',' + (size.height / 2) + ')'); + var rotatableBBox = scalable.getBBox({ target: this.paper.viewport }); + + // Store new x, y and perform rotate() again against the new rotation origin. + model.set('position', { x: rotatableBBox.x, y: rotatableBBox.y }, opt); + this.rotate(); + } + + // Update must always be called on non-rotated element. Otherwise, relative positioning + // would work with wrong (rotated) bounding boxes. + this.update(); + }, + + // Embedding mode methods. + // ----------------------- + + prepareEmbedding: function(data) { + + data || (data = {}); + + var model = data.model || this.model; + var paper = data.paper || this.paper; + var graph = paper.model; + + model.startBatch('to-front'); + + // Bring the model to the front with all his embeds. + model.toFront({ deep: true, ui: true }); + + // Note that at this point cells in the collection are not sorted by z index (it's running in the batch, see + // the dia.Graph._sortOnChangeZ), so we can't assume that the last cell in the collection has the highest z. + var maxZ = graph.get('cells').max('z').get('z'); + var connectedLinks = graph.getConnectedLinks(model, { deep: true }); + + // Move to front also all the inbound and outbound links that are connected + // to any of the element descendant. If we bring to front only embedded elements, + // links connected to them would stay in the background. + joint.util.invoke(connectedLinks, 'set', 'z', maxZ + 1, { ui: true }); + + model.stopBatch('to-front'); + + // Before we start looking for suitable parent we remove the current one. + var parentId = model.parent(); + parentId && graph.getCell(parentId).unembed(model, { ui: true }); + }, + + processEmbedding: function(data) { + + data || (data = {}); + + var model = data.model || this.model; + var paper = data.paper || this.paper; + var paperOptions = paper.options; + + var candidates = []; + if (joint.util.isFunction(paperOptions.findParentBy)) { + var parents = joint.util.toArray(paperOptions.findParentBy.call(paper.model, this)); + candidates = parents.filter(function(el) { + return el instanceof joint.dia.Cell && this.model.id !== el.id && !el.isEmbeddedIn(this.model); + }.bind(this)); + } else { + candidates = paper.model.findModelsUnderElement(model, { searchBy: paperOptions.findParentBy }); + } + + if (paperOptions.frontParentOnly) { + // pick the element with the highest `z` index + candidates = candidates.slice(-1); + } + + var newCandidateView = null; + var prevCandidateView = data.candidateEmbedView; + + // iterate over all candidates starting from the last one (has the highest z-index). + for (var i = candidates.length - 1; i >= 0; i--) { + + var candidate = candidates[i]; + + if (prevCandidateView && prevCandidateView.model.id == candidate.id) { + + // candidate remains the same + newCandidateView = prevCandidateView; + break; + + } else { + + var view = candidate.findView(paper); + if (paperOptions.validateEmbedding.call(paper, this, view)) { + + // flip to the new candidate + newCandidateView = view; + break; + } + } + } + + if (newCandidateView && newCandidateView != prevCandidateView) { + // A new candidate view found. Highlight the new one. + this.clearEmbedding(data); + data.candidateEmbedView = newCandidateView.highlight(null, { embedding: true }); + } + + if (!newCandidateView && prevCandidateView) { + // No candidate view found. Unhighlight the previous candidate. + this.clearEmbedding(data); + } + }, + + clearEmbedding: function(data) { + + data || (data = {}); + + var candidateView = data.candidateEmbedView; + if (candidateView) { + // No candidate view found. Unhighlight the previous candidate. + candidateView.unhighlight(null, { embedding: true }); + data.candidateEmbedView = null; + } + }, + + finalizeEmbedding: function(data) { + + data || (data = {}); + + var candidateView = data.candidateEmbedView; + var model = data.model || this.model; + var paper = data.paper || this.paper; + + if (candidateView) { + + // We finished embedding. Candidate view is chosen to become the parent of the model. + candidateView.model.embed(model, { ui: true }); + candidateView.unhighlight(null, { embedding: true }); + + data.candidateEmbedView = null; + } + + joint.util.invoke(paper.model.getConnectedLinks(model, { deep: true }), 'reparent', { ui: true }); + }, + + // Interaction. The controller part. + // --------------------------------- + + pointerdblclick: function(evt, x, y) { + + joint.dia.CellView.prototype.pointerdblclick.apply(this, arguments); + this.notify('element:pointerdblclick', evt, x, y); + }, + + pointerclick: function(evt, x, y) { + + joint.dia.CellView.prototype.pointerclick.apply(this, arguments); + this.notify('element:pointerclick', evt, x, y); + }, + + contextmenu: function(evt, x, y) { + + joint.dia.CellView.prototype.contextmenu.apply(this, arguments); + this.notify('element:contextmenu', evt, x, y); + }, + + pointerdown: function(evt, x, y) { + + joint.dia.CellView.prototype.pointerdown.apply(this, arguments); + this.notify('element:pointerdown', evt, x, y); + + this.dragStart(evt, x, y); + }, + + pointermove: function(evt, x, y) { + + var data = this.eventData(evt); + switch (data.action) { + case 'move': + this.drag(evt, x, y); + break; + case 'magnet': + this.dragMagnet(evt, x, y); + break; + } + + if (!data.stopPropagation) { + joint.dia.CellView.prototype.pointermove.apply(this, arguments); + this.notify('element:pointermove', evt, x, y); + } + + // Make sure the element view data is passed along. + // It could have been wiped out in the handlers above. + this.eventData(evt, data); + }, + + pointerup: function(evt, x, y) { + + var data = this.eventData(evt); + switch (data.action) { + case 'move': + this.dragEnd(evt, x, y); + break; + case 'magnet': + this.dragMagnetEnd(evt, x, y); + return; + } + + if (!data.stopPropagation) { + this.notify('element:pointerup', evt, x, y); + joint.dia.CellView.prototype.pointerup.apply(this, arguments); + } + }, + + mouseover: function(evt) { + + joint.dia.CellView.prototype.mouseover.apply(this, arguments); + this.notify('element:mouseover', evt); + }, + + mouseout: function(evt) { + + joint.dia.CellView.prototype.mouseout.apply(this, arguments); + this.notify('element:mouseout', evt); + }, + + mouseenter: function(evt) { + + joint.dia.CellView.prototype.mouseenter.apply(this, arguments); + this.notify('element:mouseenter', evt); + }, + + mouseleave: function(evt) { + + joint.dia.CellView.prototype.mouseleave.apply(this, arguments); + this.notify('element:mouseleave', evt); + }, + + mousewheel: function(evt, x, y, delta) { + + joint.dia.CellView.prototype.mousewheel.apply(this, arguments); + this.notify('element:mousewheel', evt, x, y, delta); + }, + + onmagnet: function(evt, x, y) { + + this.dragMagnetStart(evt, x, y); + + var stopPropagation = this.eventData(evt).stopPropagation; + if (stopPropagation) evt.stopPropagation(); + }, + + // Drag Start Handlers + + dragStart: function(evt, x, y) { + + if (!this.can('elementMove')) return; + + this.eventData(evt, { + action: 'move', + x: x, + y: y, + restrictedArea: this.paper.getRestrictedArea(this) + }); + }, + + dragMagnetStart: function(evt, x, y) { + + if (!this.can('addLinkFromMagnet')) return; + + this.model.startBatch('add-link'); + + var paper = this.paper; + var graph = paper.model; + var magnet = evt.target; + var link = paper.getDefaultLink(this, magnet); + var sourceEnd = this.getLinkEnd(magnet, x, y, link, 'source'); + var targetEnd = { x: x, y: y }; + + link.set({ source: sourceEnd, target: targetEnd }); + link.addTo(graph, { async: false, ui: true }); + + var linkView = link.findView(paper); + joint.dia.CellView.prototype.pointerdown.apply(linkView, arguments); + linkView.notify('link:pointerdown', evt, x, y); + var data = linkView.startArrowheadMove('target', { whenNotAllowed: 'remove' }); + linkView.eventData(evt, data); + + this.eventData(evt, { + action: 'magnet', + linkView: linkView, + stopPropagation: true + }); + + this.paper.delegateDragEvents(this, evt.data); + }, + + // Drag Handlers + + drag: function(evt, x, y) { + + var paper = this.paper; + var grid = paper.options.gridSize; + var element = this.model; + var position = element.position(); + var data = this.eventData(evt); + + // Make sure the new element's position always snaps to the current grid after + // translate as the previous one could be calculated with a different grid size. + var tx = g.snapToGrid(position.x, grid) - position.x + g.snapToGrid(x - data.x, grid); + var ty = g.snapToGrid(position.y, grid) - position.y + g.snapToGrid(y - data.y, grid); + + element.translate(tx, ty, { restrictedArea: data.restrictedArea, ui: true }); + + var embedding = !!data.embedding; + if (paper.options.embeddingMode) { + if (!embedding) { + // Prepare the element for embedding only if the pointer moves. + // We don't want to do unnecessary action with the element + // if an user only clicks/dblclicks on it. + this.prepareEmbedding(data); + embedding = true; + } + this.processEmbedding(data); + } + + this.eventData(evt, { + x: g.snapToGrid(x, grid), + y: g.snapToGrid(y, grid), + embedding: embedding + }); + }, + + dragMagnet: function(evt, x, y) { + + var data = this.eventData(evt); + var linkView = data.linkView; + if (linkView) linkView.pointermove(evt, x, y); + }, + + // Drag End Handlers + + dragEnd: function(evt, x, y) { + + var data = this.eventData(evt); + if (data.embedding) this.finalizeEmbedding(data); + }, + + dragMagnetEnd: function(evt, x, y) { + + var data = this.eventData(evt); + var linkView = data.linkView; + if (linkView) linkView.pointerup(evt, x, y); + + this.model.stopBatch('add-link'); + } + +}); + + +// joint.dia.Link base model. +// -------------------------- + +joint.dia.Link = joint.dia.Cell.extend({ + + // The default markup for links. + markup: [ + '<path class="connection" stroke="black" d="M 0 0 0 0"/>', + '<path class="marker-source" fill="black" stroke="black" d="M 0 0 0 0"/>', + '<path class="marker-target" fill="black" stroke="black" d="M 0 0 0 0"/>', + '<path class="connection-wrap" d="M 0 0 0 0"/>', + '<g class="labels"/>', + '<g class="marker-vertices"/>', + '<g class="marker-arrowheads"/>', + '<g class="link-tools"/>' + ].join(''), + + toolMarkup: [ + '<g class="link-tool">', + '<g class="tool-remove" event="remove">', + '<circle r="11" />', + '<path transform="scale(.8) translate(-16, -16)" d="M24.778,21.419 19.276,15.917 24.777,10.415 21.949,7.585 16.447,13.087 10.945,7.585 8.117,10.415 13.618,15.917 8.116,21.419 10.946,24.248 16.447,18.746 21.948,24.248z" />', + '<title>Remove link.', + '', + '', + '', + '', + 'Link options.', + '', + '' + ].join(''), + + doubleToolMarkup: undefined, + + // The default markup for showing/removing vertices. These elements are the children of the .marker-vertices element (see `this.markup`). + // Only .marker-vertex and .marker-vertex-remove element have special meaning. The former is used for + // dragging vertices (changin their position). The latter is used for removing vertices. + vertexMarkup: [ + '', + '', + '', + '', + 'Remove vertex.', + '', + '' + ].join(''), + + arrowheadMarkup: [ + '', + '', + '' + ].join(''), + + // may be overwritten by user to change default label (its markup, attrs, position) + defaultLabel: undefined, + + // deprecated + // may be overwritten by user to change default label markup + // lower priority than defaultLabel.markup + labelMarkup: undefined, + + // private + _builtins: { + defaultLabel: { + // builtin default markup: + // used if neither defaultLabel.markup + // nor label.markup is set + markup: [ + { + tagName: 'rect', + selector: 'rect' // faster than tagName CSS selector + }, { + tagName: 'text', + selector: 'text' // faster than tagName CSS selector + } + ], + // builtin default attributes: + // applied only if builtin default markup is used + attrs: { + text: { + fill: '#000000', + fontSize: 14, + textAnchor: 'middle', + yAlignment: 'middle', + pointerEvents: 'none' + }, + rect: { + ref: 'text', + fill: '#ffffff', + rx: 3, + ry: 3, + refWidth: 1, + refHeight: 1, + refX: 0, + refY: 0 + } + }, + // builtin default position: + // used if neither defaultLabel.position + // nor label.position is set + position: { + distance: 0.5 + } + } + }, + + defaults: { + type: 'link', + source: {}, + target: {} + }, + + isLink: function() { + + return true; + }, + + disconnect: function(opt) { + + return this.set({ + source: { x: 0, y: 0 }, + target: { x: 0, y: 0 } + }, opt); + }, + + source: function(source, args, opt) { + + // getter + if (source === undefined) { + return joint.util.clone(this.get('source')); + } + + // setter + var localSource; + var localOpt; + + // `source` is a cell + // take only its `id` and combine with `args` + var isCellProvided = source instanceof joint.dia.Cell; + if (isCellProvided) { // three arguments + localSource = joint.util.clone(args) || {}; + localSource.id = source.id; + localOpt = opt; + return this.set('source', localSource, localOpt); + } + + // `source` is a g.Point + // take only its `x` and `y` and combine with `args` + var isPointProvided = source instanceof g.Point; + if (isPointProvided) { // three arguments + localSource = joint.util.clone(args) || {}; + localSource.x = source.x; + localSource.y = source.y; + localOpt = opt; + return this.set('source', localSource, localOpt); + } + + // `source` is an object + // no checking + // two arguments + localSource = source; + localOpt = args; + return this.set('source', localSource, localOpt); + }, + + target: function(target, args, opt) { + + // getter + if (target === undefined) { + return joint.util.clone(this.get('target')); + } + + // setter + var localTarget; + var localOpt; + + // `target` is a cell + // take only its `id` argument and combine with `args` + var isCellProvided = target instanceof joint.dia.Cell; + if (isCellProvided) { // three arguments + localTarget = joint.util.clone(args) || {}; + localTarget.id = target.id; + localOpt = opt; + return this.set('target', localTarget, localOpt); + } + + // `target` is a g.Point + // take only its `x` and `y` and combine with `args` + var isPointProvided = target instanceof g.Point; + if (isPointProvided) { // three arguments + localTarget = joint.util.clone(args) || {}; + localTarget.x = target.x; + localTarget.y = target.y; + localOpt = opt; + return this.set('target', localTarget, localOpt); + } + + // `target` is an object + // no checking + // two arguments + localTarget = target; + localOpt = args; + return this.set('target', localTarget, localOpt); + }, + + router: function(name, args, opt) { + + // getter + if (name === undefined) { + var router = this.get('router'); + if (!router) { + if (this.get('manhattan')) return { name: 'orthogonal' }; // backwards compatibility + return null; + } + if (typeof router === 'object') return joint.util.clone(router); + return router; // e.g. a function + } + + // setter + var isRouterProvided = ((typeof name === 'object') || (typeof name === 'function')); + var localRouter = isRouterProvided ? name : { name: name, args: args }; + var localOpt = isRouterProvided ? args : opt; + + return this.set('router', localRouter, localOpt); + }, + + connector: function(name, args, opt) { + + // getter + if (name === undefined) { + var connector = this.get('connector'); + if (!connector) { + if (this.get('smooth')) return { name: 'smooth' }; // backwards compatibility + return null; + } + if (typeof connector === 'object') return joint.util.clone(connector); + return connector; // e.g. a function + } + + // setter + var isConnectorProvided = ((typeof name === 'object' || typeof name === 'function')); + var localConnector = isConnectorProvided ? name : { name: name, args: args }; + var localOpt = isConnectorProvided ? args : opt; + + return this.set('connector', localConnector, localOpt); + }, + + // Labels API + + // A convenient way to set labels. Currently set values will be mixined with `value` if used as a setter. + label: function(idx, label, opt) { + + var labels = this.labels(); + + idx = (isFinite(idx) && idx !== null) ? (idx | 0) : 0; + if (idx < 0) idx = labels.length + idx; + + // getter + if (arguments.length <= 1) return this.prop(['labels', idx]); + // setter + return this.prop(['labels', idx], label, opt); + }, + + labels: function(labels, opt) { + + // getter + if (arguments.length === 0) { + labels = this.get('labels'); + if (!Array.isArray(labels)) return []; + return labels.slice(); + } + // setter + if (!Array.isArray(labels)) labels = []; + return this.set('labels', labels, opt); + }, + + insertLabel: function(idx, label, opt) { + + if (!label) throw new Error('dia.Link: no label provided'); + + var labels = this.labels(); + var n = labels.length; + idx = (isFinite(idx) && idx !== null) ? (idx | 0) : n; + if (idx < 0) idx = n + idx + 1; + + labels.splice(idx, 0, label); + return this.labels(labels, opt); + }, + + // convenience function + // add label to end of labels array + appendLabel: function(label, opt) { + + return this.insertLabel(-1, label, opt); + }, + + removeLabel: function(idx, opt) { + + var labels = this.labels(); + idx = (isFinite(idx) && idx !== null) ? (idx | 0) : -1; + + labels.splice(idx, 1); + return this.labels(labels, opt); + }, + + // Vertices API + + vertex: function(idx, vertex, opt) { + + var vertices = this.vertices(); + + idx = (isFinite(idx) && idx !== null) ? (idx | 0) : 0; + if (idx < 0) idx = vertices.length + idx; + + // getter + if (arguments.length <= 1) return this.prop(['vertices', idx]); + // setter + return this.prop(['vertices', idx], vertex, opt); + }, + + vertices: function(vertices, opt) { + + // getter + if (arguments.length === 0) { + vertices = this.get('vertices'); + if (!Array.isArray(vertices)) return []; + return vertices.slice(); + } + // setter + if (!Array.isArray(vertices)) vertices = []; + return this.set('vertices', vertices, opt); + }, + + insertVertex: function(idx, vertex, opt) { + + if (!vertex) throw new Error('dia.Link: no vertex provided'); + + var vertices = this.vertices(); + var n = vertices.length; + idx = (isFinite(idx) && idx !== null) ? (idx | 0) : n; + if (idx < 0) idx = n + idx + 1; + + vertices.splice(idx, 0, vertex); + return this.vertices(vertices, opt); + }, + + removeVertex: function(idx, opt) { + + var vertices = this.vertices(); + idx = (isFinite(idx) && idx !== null) ? (idx | 0) : -1; + + vertices.splice(idx, 1); + return this.vertices(vertices, opt); + }, + + // Transformations + + translate: function(tx, ty, opt) { + + // enrich the option object + opt = opt || {}; + opt.translateBy = opt.translateBy || this.id; + opt.tx = tx; + opt.ty = ty; + + return this.applyToPoints(function(p) { + return { x: (p.x || 0) + tx, y: (p.y || 0) + ty }; + }, opt); + }, + + scale: function(sx, sy, origin, opt) { + + return this.applyToPoints(function(p) { + return g.point(p).scale(sx, sy, origin).toJSON(); + }, opt); + }, + + applyToPoints: function(fn, opt) { + + if (!joint.util.isFunction(fn)) { + throw new TypeError('dia.Link: applyToPoints expects its first parameter to be a function.'); + } + + var attrs = {}; + + var source = this.source(); + if (!source.id) { + attrs.source = fn(source); + } + + var target = this.target(); + if (!target.id) { + attrs.target = fn(target); + } + + var vertices = this.vertices(); + if (vertices.length > 0) { + attrs.vertices = vertices.map(fn); + } + + return this.set(attrs, opt); + }, + + reparent: function(opt) { + + var newParent; + + if (this.graph) { + + var source = this.getSourceElement(); + var target = this.getTargetElement(); + var prevParent = this.getParentCell(); + + if (source && target) { + newParent = this.graph.getCommonAncestor(source, target); + } + + if (prevParent && (!newParent || newParent.id !== prevParent.id)) { + // Unembed the link if source and target has no common ancestor + // or common ancestor changed + prevParent.unembed(this, opt); + } + + if (newParent) { + newParent.embed(this, opt); + } + } + + return newParent; + }, + + hasLoop: function(opt) { + + opt = opt || {}; + + var sourceId = this.source().id; + var targetId = this.target().id; + + if (!sourceId || !targetId) { + // Link "pinned" to the paper does not have a loop. + return false; + } + + var loop = sourceId === targetId; + + // Note that there in the deep mode a link can have a loop, + // even if it connects only a parent and its embed. + // A loop "target equals source" is valid in both shallow and deep mode. + if (!loop && opt.deep && this.graph) { + + var sourceElement = this.getSourceElement(); + var targetElement = this.getTargetElement(); + + loop = sourceElement.isEmbeddedIn(targetElement) || targetElement.isEmbeddedIn(sourceElement); + } + + return loop; + }, + + // unlike source(), this method returns null if source is a point + getSourceElement: function() { + + var source = this.source(); + var graph = this.graph; + + return (source && source.id && graph && graph.getCell(source.id)) || null; + }, + + // unlike target(), this method returns null if target is a point + getTargetElement: function() { + + var target = this.target(); + var graph = this.graph; + + return (target && target.id && graph && graph.getCell(target.id)) || null; + }, + + // Returns the common ancestor for the source element, + // target element and the link itself. + getRelationshipAncestor: function() { + + var connectionAncestor; + + if (this.graph) { + + var cells = [ + this, + this.getSourceElement(), // null if source is a point + this.getTargetElement() // null if target is a point + ].filter(function(item) { + return !!item; + }); + + connectionAncestor = this.graph.getCommonAncestor.apply(this.graph, cells); + } + + return connectionAncestor || null; + }, + + // Is source, target and the link itself embedded in a given cell? + isRelationshipEmbeddedIn: function(cell) { + + var cellId = (joint.util.isString(cell) || joint.util.isNumber(cell)) ? cell : cell.id; + var ancestor = this.getRelationshipAncestor(); + + return !!ancestor && (ancestor.id === cellId || ancestor.isEmbeddedIn(cellId)); + }, + + // Get resolved default label. + _getDefaultLabel: function() { + + var defaultLabel = this.get('defaultLabel') || this.defaultLabel || {}; + + var label = {}; + label.markup = defaultLabel.markup || this.get('labelMarkup') || this.labelMarkup; + label.position = defaultLabel.position; + label.attrs = defaultLabel.attrs; + label.size = defaultLabel.size; + + return label; + } +}, + { + endsEqual: function(a, b) { + + var portsEqual = a.port === b.port || !a.port && !b.port; + return a.id === b.id && portsEqual; + } + }); + + +// joint.dia.Link base view and controller. +// ---------------------------------------- + +joint.dia.LinkView = joint.dia.CellView.extend({ + + className: function() { + + var classNames = joint.dia.CellView.prototype.className.apply(this).split(' '); + + classNames.push('link'); + + return classNames.join(' '); + }, + + options: { + + shortLinkLength: 105, + doubleLinkTools: false, + longLinkLength: 155, + linkToolsOffset: 40, + doubleLinkToolsOffset: 65, + sampleInterval: 50, + }, + + _labelCache: null, + _labelSelectors: null, + _markerCache: null, + _V: null, + _dragData: null, // deprecated + + metrics: null, + decimalsRounding: 2, + + initialize: function(options) { + + joint.dia.CellView.prototype.initialize.apply(this, arguments); + + // create methods in prototype, so they can be accessed from any instance and + // don't need to be create over and over + if (typeof this.constructor.prototype.watchSource !== 'function') { + this.constructor.prototype.watchSource = this.createWatcher('source'); + this.constructor.prototype.watchTarget = this.createWatcher('target'); + } + + // `_.labelCache` is a mapping of indexes of labels in the `this.get('labels')` array to + // `` nodes wrapped by Vectorizer. This allows for quick access to the + // nodes in `updateLabelPosition()` in order to update the label positions. + this._labelCache = {}; + + // a cache of label selectors + this._labelSelectors = {}; + + // keeps markers bboxes and positions again for quicker access + this._markerCache = {}; + + // cache of default markup nodes + this._V = {}, + + // connection path metrics + this.metrics = {}, + + // bind events + this.startListening(); + }, + + startListening: function() { + + var model = this.model; + + this.listenTo(model, 'change:markup', this.render); + this.listenTo(model, 'change:smooth change:manhattan change:router change:connector', this.update); + this.listenTo(model, 'change:toolMarkup', this.onToolsChange); + this.listenTo(model, 'change:labels change:labelMarkup', this.onLabelsChange); + this.listenTo(model, 'change:vertices change:vertexMarkup', this.onVerticesChange); + this.listenTo(model, 'change:source', this.onSourceChange); + this.listenTo(model, 'change:target', this.onTargetChange); + }, + + onSourceChange: function(cell, source, opt) { + + // Start watching the new source model. + this.watchSource(cell, source); + // This handler is called when the source attribute is changed. + // This can happen either when someone reconnects the link (or moves arrowhead), + // or when an embedded link is translated by its ancestor. + // 1. Always do update. + // 2. Do update only if the opposite end ('target') is also a point. + var model = this.model; + if (!opt.translateBy || !model.get('target').id || !source.id) { + this.update(model, null, opt); + } + }, + + onTargetChange: function(cell, target, opt) { + + // Start watching the new target model. + this.watchTarget(cell, target); + // See `onSourceChange` method. + var model = this.model; + if (!opt.translateBy || (model.get('source').id && !target.id && joint.util.isEmpty(model.get('vertices')))) { + this.update(model, null, opt); + } + }, + + onVerticesChange: function(cell, changed, opt) { + + this.renderVertexMarkers(); + + // If the vertices have been changed by a translation we do update only if the link was + // the only link that was translated. If the link was translated via another element which the link + // is embedded in, this element will be translated as well and that triggers an update. + // Note that all embeds in a model are sorted - first comes links, then elements. + if (!opt.translateBy || opt.translateBy === this.model.id) { + // Vertices were changed (not as a reaction on translate) + // or link.translate() was called or + this.update(cell, null, opt); + } + }, + + onToolsChange: function() { + + this.renderTools().updateToolsPosition(); + }, + + onLabelsChange: function(link, labels, opt) { + + var requireRender = true; + + var previousLabels = this.model.previous('labels'); + + if (previousLabels) { + // Here is an optimalization for cases when we know, that change does + // not require rerendering of all labels. + if (('propertyPathArray' in opt) && ('propertyValue' in opt)) { + // The label is setting by `prop()` method + var pathArray = opt.propertyPathArray || []; + var pathLength = pathArray.length; + if (pathLength > 1) { + // We are changing a single label here e.g. 'labels/0/position' + var labelExists = !!previousLabels[pathArray[1]]; + if (labelExists) { + if (pathLength === 2) { + // We are changing the entire label. Need to check if the + // markup is also being changed. + requireRender = ('markup' in Object(opt.propertyValue)); + } else if (pathArray[2] !== 'markup') { + // We are changing a label property but not the markup + requireRender = false; + } + } + } + } + } + + if (requireRender) { + this.renderLabels(); + } else { + this.updateLabels(); + } + + this.updateLabelPositions(); + }, + + // Rendering. + // ---------- + + render: function() { + + this.vel.empty(); + this._V = {}; + this.renderMarkup(); + // rendering labels has to be run after the link is appended to DOM tree. (otherwise bbox + // returns zero values) + this.renderLabels(); + // start watching the ends of the link for changes + var model = this.model; + this.watchSource(model, model.source()) + .watchTarget(model, model.target()) + .update(); + + return this; + }, + + renderMarkup: function() { + + var link = this.model; + var markup = link.get('markup') || link.markup; + if (!markup) throw new Error('dia.LinkView: markup required'); + if (Array.isArray(markup)) return this.renderJSONMarkup(markup); + if (typeof markup === 'string') return this.renderStringMarkup(markup); + throw new Error('dia.LinkView: invalid markup'); + }, + + renderJSONMarkup: function(markup) { + + var doc = joint.util.parseDOMJSON(markup); + // Selectors + var selectors = this.selectors = doc.selectors; + var rootSelector = this.selector; + if (selectors[rootSelector]) throw new Error('dia.LinkView: ambiguous root selector.'); + selectors[rootSelector] = this.el; + // Fragment + this.vel.append(doc.fragment); + }, + + renderStringMarkup: function(markup) { + + // A special markup can be given in the `properties.markup` property. This might be handy + // if e.g. arrowhead markers should be `` elements or any other element than ``s. + // `.connection`, `.connection-wrap`, `.marker-source` and `.marker-target` selectors + // of elements with special meaning though. Therefore, those classes should be preserved in any + // special markup passed in `properties.markup`. + var children = V(markup); + // custom markup may contain only one children + if (!Array.isArray(children)) children = [children]; + // Cache all children elements for quicker access. + var cache = this._V; // vectorized markup; + for (var i = 0, n = children.length; i < n; i++) { + var child = children[i]; + var className = child.attr('class'); + if (className) { + // Strip the joint class name prefix, if there is one. + className = joint.util.removeClassNamePrefix(className); + cache[$.camelCase(className)] = child; + } + } + // partial rendering + this.renderTools(); + this.renderVertexMarkers(); + this.renderArrowheadMarkers(); + this.vel.append(children); + }, + + _getLabelMarkup: function(labelMarkup) { + + if (!labelMarkup) return undefined; + + if (Array.isArray(labelMarkup)) return this._getLabelJSONMarkup(labelMarkup); + if (typeof labelMarkup === 'string') return this._getLabelStringMarkup(labelMarkup); + throw new Error('dia.linkView: invalid label markup'); + }, + + _getLabelJSONMarkup: function(labelMarkup) { + + return joint.util.parseDOMJSON(labelMarkup); // fragment and selectors + }, + + _getLabelStringMarkup: function(labelMarkup) { + + var children = V(labelMarkup); + var fragment = document.createDocumentFragment(); + + if (!Array.isArray(children)) { + fragment.append(children.node); + + } else { + for (var i = 0, n = children.length; i < n; i++) { + var currentChild = children[i].node; + fragment.appendChild(currentChild); + } + } + + return { fragment: fragment, selectors: {} }; // no selectors + }, + + // Label markup fragment may come wrapped in , or not. + // If it doesn't, add the container here. + _normalizeLabelMarkup: function(markup) { + + if (!markup) return undefined; + + var fragment = markup.fragment; + if (!(markup.fragment instanceof DocumentFragment) || !markup.fragment.hasChildNodes()) throw new Error('dia.LinkView: invalid label markup.'); + + var vNode; + var childNodes = fragment.childNodes; + + if ((childNodes.length > 1) || childNodes[0].nodeName.toUpperCase() !== 'G') { + // default markup fragment is not wrapped in + // add a container + + vNode = V('g'); + vNode.append(fragment); + vNode.addClass('label'); + + } else { + vNode = V(childNodes[0]); + vNode.addClass('label'); + } + + return { node: vNode.node, selectors: markup.selectors }; + }, + + renderLabels: function() { + + var cache = this._V; + var vLabels = cache.labels; + var labelCache = this._labelCache = {}; + var labelSelectors = this._labelSelectors = {}; + + if (vLabels) vLabels.empty(); + + var model = this.model; + var labels = model.get('labels') || []; + var labelsCount = labels.length; + if (labelsCount === 0) return this; + + if (!vLabels) { + // there is no label container in the markup but some labels are defined + // add a container + vLabels = cache.labels = V('g').addClass('labels').appendTo(this.el); + } + + for (var i = 0; i < labelsCount; i++) { + + var label = labels[i]; + var labelMarkup = this._normalizeLabelMarkup(this._getLabelMarkup(label.markup)); + + var node; + var selectors; + if (labelMarkup) { + node = labelMarkup.node; + selectors = labelMarkup.selectors; + + } else { + var builtinDefaultLabel = model._builtins.defaultLabel; + var builtinDefaultLabelMarkup = this._normalizeLabelMarkup(this._getLabelMarkup(builtinDefaultLabel.markup)); + + var defaultLabel = model._getDefaultLabel(); + var defaultLabelMarkup = this._normalizeLabelMarkup(this._getLabelMarkup(defaultLabel.markup)); + + var defaultMarkup = defaultLabelMarkup || builtinDefaultLabelMarkup; + + node = defaultMarkup.node; + selectors = defaultMarkup.selectors; + } + + var vLabel = V(node); + vLabel.attr('label-idx', i); // assign label-idx + vLabel.appendTo(vLabels); + labelCache[i] = vLabel; // cache node for `updateLabels()` so it can just update label node positions + + selectors[this.selector] = vLabel.node; + labelSelectors[i] = selectors; // cache label selectors for `updateLabels()` + } + + this.updateLabels(); + + return this; + }, + + // merge default label attrs into label attrs + // keep `undefined` or `null` because `{}` means something else + _mergeLabelAttrs: function(hasCustomMarkup, labelAttrs, defaultLabelAttrs, builtinDefaultLabelAttrs) { + + if (labelAttrs === null) return null; + if (labelAttrs === undefined) { + + if (defaultLabelAttrs === null) return null; + if (defaultLabelAttrs === undefined) { + + if (hasCustomMarkup) return undefined; + return builtinDefaultLabelAttrs; + } + + if (hasCustomMarkup) return defaultLabelAttrs; + return joint.util.merge({}, builtinDefaultLabelAttrs, defaultLabelAttrs); + } + + if (hasCustomMarkup) return joint.util.merge({}, defaultLabelAttrs, labelAttrs); + return joint.util.merge({}, builtinDefaultLabelAttrs, defaultLabelAttrs, labelAttrs); + }, + + updateLabels: function() { + + if (!this._V.labels) return this; + + var model = this.model; + var labels = model.get('labels') || []; + var canLabelMove = this.can('labelMove'); + + var builtinDefaultLabel = model._builtins.defaultLabel; + var builtinDefaultLabelAttrs = builtinDefaultLabel.attrs; + + var defaultLabel = model._getDefaultLabel(); + var defaultLabelMarkup = defaultLabel.markup; + var defaultLabelAttrs = defaultLabel.attrs; + + for (var i = 0, n = labels.length; i < n; i++) { + + var vLabel = this._labelCache[i]; + vLabel.attr('cursor', (canLabelMove ? 'move' : 'default')); + + var selectors = this._labelSelectors[i]; + + var label = labels[i]; + var labelMarkup = label.markup; + var labelAttrs = label.attrs; + + var attrs = this._mergeLabelAttrs( + (labelMarkup || defaultLabelMarkup), + labelAttrs, + defaultLabelAttrs, + builtinDefaultLabelAttrs + ); + + this.updateDOMSubtreeAttributes(vLabel.node, attrs, { + rootBBox: new g.Rect(label.size), + selectors: selectors + }); + } + + return this; + }, + + renderTools: function() { + + if (!this._V.linkTools) return this; + + // Tools are a group of clickable elements that manipulate the whole link. + // A good example of this is the remove tool that removes the whole link. + // Tools appear after hovering the link close to the `source` element/point of the link + // but are offset a bit so that they don't cover the `marker-arrowhead`. + + var $tools = $(this._V.linkTools.node).empty(); + var toolTemplate = joint.util.template(this.model.get('toolMarkup') || this.model.toolMarkup); + var tool = V(toolTemplate()); + + $tools.append(tool.node); + + // Cache the tool node so that the `updateToolsPosition()` can update the tool position quickly. + this._toolCache = tool; + + // If `doubleLinkTools` is enabled, we render copy of the tools on the other side of the + // link as well but only if the link is longer than `longLinkLength`. + if (this.options.doubleLinkTools) { + + var tool2; + if (this.model.get('doubleToolMarkup') || this.model.doubleToolMarkup) { + toolTemplate = joint.util.template(this.model.get('doubleToolMarkup') || this.model.doubleToolMarkup); + tool2 = V(toolTemplate()); + } else { + tool2 = tool.clone(); + } + + $tools.append(tool2.node); + this._tool2Cache = tool2; + } + + return this; + }, + + renderVertexMarkers: function() { + + if (!this._V.markerVertices) return this; + + var $markerVertices = $(this._V.markerVertices.node).empty(); + + // A special markup can be given in the `properties.vertexMarkup` property. This might be handy + // if default styling (elements) are not desired. This makes it possible to use any + // SVG elements for .marker-vertex and .marker-vertex-remove tools. + var markupTemplate = joint.util.template(this.model.get('vertexMarkup') || this.model.vertexMarkup); + + this.model.vertices().forEach(function(vertex, idx) { + + $markerVertices.append(V(markupTemplate(joint.util.assign({ idx: idx }, vertex))).node); + }); + + return this; + }, + + renderArrowheadMarkers: function() { + + // Custom markups might not have arrowhead markers. Therefore, jump of this function immediately if that's the case. + if (!this._V.markerArrowheads) return this; + + var $markerArrowheads = $(this._V.markerArrowheads.node); + + $markerArrowheads.empty(); + + // A special markup can be given in the `properties.vertexMarkup` property. This might be handy + // if default styling (elements) are not desired. This makes it possible to use any + // SVG elements for .marker-vertex and .marker-vertex-remove tools. + var markupTemplate = joint.util.template(this.model.get('arrowheadMarkup') || this.model.arrowheadMarkup); + + this._V.sourceArrowhead = V(markupTemplate({ end: 'source' })); + this._V.targetArrowhead = V(markupTemplate({ end: 'target' })); + + $markerArrowheads.append(this._V.sourceArrowhead.node, this._V.targetArrowhead.node); + + return this; + }, + + // Updating. + // --------- + + // Default is to process the `attrs` object and set attributes on subelements based on the selectors. + update: function(model, attributes, opt) { + + opt || (opt = {}); + + // update the link path + this.updateConnection(opt); + + // update SVG attributes defined by 'attrs/'. + this.updateDOMSubtreeAttributes(this.el, this.model.attr(), { selectors: this.selectors }); + + this.updateDefaultConnectionPath(); + + // update the label position etc. + this.updateLabelPositions(); + this.updateToolsPosition(); + this.updateArrowheadMarkers(); + + this.updateTools(opt); + // Local perpendicular flag (as opposed to one defined on paper). + // Could be enabled inside a connector/router. It's valid only + // during the update execution. + this.options.perpendicular = null; + // Mark that postponed update has been already executed. + this.updatePostponed = false; + + return this; + }, + + removeRedundantLinearVertices: function(opt) { + var link = this.model; + var vertices = link.vertices(); + var conciseVertices = []; + var n = vertices.length; + var m = 0; + for (var i = 0; i < n; i++) { + var current = new g.Point(vertices[i]).round(); + var prev = new g.Point(conciseVertices[m - 1] || this.sourceAnchor).round(); + if (prev.equals(current)) continue; + var next = new g.Point(vertices[i + 1] || this.targetAnchor).round(); + if (prev.equals(next)) continue; + var line = new g.Line(prev, next); + if (line.pointOffset(current) === 0) continue; + conciseVertices.push(vertices[i]); + m++; + } + if (n === m) return 0; + link.vertices(conciseVertices, opt); + return (n - m); + }, + + updateDefaultConnectionPath: function() { + + var cache = this._V; + + if (cache.connection) { + cache.connection.attr('d', this.getSerializedConnection()); + } + + if (cache.connectionWrap) { + cache.connectionWrap.attr('d', this.getSerializedConnection()); + } + + if (cache.markerSource && cache.markerTarget) { + this._translateAndAutoOrientArrows(cache.markerSource, cache.markerTarget); + } + }, + + getEndView: function(type) { + switch (type) { + case 'source': + return this.sourceView || null; + case 'target': + return this.targetView || null; + default: + throw new Error('dia.LinkView: type parameter required.'); + } + }, + + getEndAnchor: function(type) { + switch (type) { + case 'source': + return new g.Point(this.sourceAnchor); + case 'target': + return new g.Point(this.targetAnchor); + default: + throw new Error('dia.LinkView: type parameter required.'); + } + }, + + getEndMagnet: function(type) { + switch (type) { + case 'source': + var sourceView = this.sourceView; + if (!sourceView) break; + return this.sourceMagnet || sourceView.el; + case 'target': + var targetView = this.targetView; + if (!targetView) break; + return this.targetMagnet || targetView.el; + default: + throw new Error('dia.LinkView: type parameter required.'); + } + return null; + }, + + updateConnection: function(opt) { + + opt = opt || {}; + + var model = this.model; + var route, path; + + if (opt.translateBy && model.isRelationshipEmbeddedIn(opt.translateBy)) { + // The link is being translated by an ancestor that will + // shift source point, target point and all vertices + // by an equal distance. + var tx = opt.tx || 0; + var ty = opt.ty || 0; + + route = (new g.Polyline(this.route)).translate(tx, ty).points; + + // translate source and target connection and marker points. + this._translateConnectionPoints(tx, ty); + + // translate the path itself + path = this.path; + path.translate(tx, ty); + + } else { + + var vertices = model.vertices(); + // 1. Find Anchors + + var anchors = this.findAnchors(vertices); + var sourceAnchor = this.sourceAnchor = anchors.source; + var targetAnchor = this.targetAnchor = anchors.target; + + // 2. Find Route + route = this.findRoute(vertices, opt); + + // 3. Find Connection Points + var connectionPoints = this.findConnectionPoints(route, sourceAnchor, targetAnchor); + var sourcePoint = this.sourcePoint = connectionPoints.source; + var targetPoint = this.targetPoint = connectionPoints.target; + + // 3b. Find Marker Connection Point - Backwards Compatibility + var markerPoints = this.findMarkerPoints(route, sourcePoint, targetPoint); + + // 4. Find Connection + path = this.findPath(route, markerPoints.source || sourcePoint, markerPoints.target || targetPoint); + } + + this.route = route; + this.path = path; + this.metrics = {}; + }, + + findMarkerPoints: function(route, sourcePoint, targetPoint) { + + var firstWaypoint = route[0]; + var lastWaypoint = route[route.length - 1]; + + // Move the source point by the width of the marker taking into account + // its scale around x-axis. Note that scale is the only transform that + // makes sense to be set in `.marker-source` attributes object + // as all other transforms (translate/rotate) will be replaced + // by the `translateAndAutoOrient()` function. + var cache = this._markerCache; + // cache source and target points + var sourceMarkerPoint, targetMarkerPoint; + + if (this._V.markerSource) { + + cache.sourceBBox = cache.sourceBBox || this._V.markerSource.getBBox(); + sourceMarkerPoint = g.point(sourcePoint).move( + firstWaypoint || targetPoint, + cache.sourceBBox.width * this._V.markerSource.scale().sx * -1 + ).round(); + } + + if (this._V.markerTarget) { + + cache.targetBBox = cache.targetBBox || this._V.markerTarget.getBBox(); + targetMarkerPoint = g.point(targetPoint).move( + lastWaypoint || sourcePoint, + cache.targetBBox.width * this._V.markerTarget.scale().sx * -1 + ).round(); + } + + // if there was no markup for the marker, use the connection point. + cache.sourcePoint = sourceMarkerPoint || sourcePoint.clone(); + cache.targetPoint = targetMarkerPoint || targetPoint.clone(); + + return { + source: sourceMarkerPoint, + target: targetMarkerPoint + } + }, + + findAnchors: function(vertices) { + + var model = this.model; + var firstVertex = vertices[0]; + var lastVertex = vertices[vertices.length - 1]; + var sourceDef = model.get('source'); + var targetDef = model.get('target'); + var sourceView = this.sourceView; + var targetView = this.targetView; + var sourceMagnet, targetMagnet; + + // Anchor Source + var sourceAnchor; + if (sourceView) { + sourceMagnet = (this.sourceMagnet || sourceView.el); + var sourceAnchorRef; + if (firstVertex) { + sourceAnchorRef = new g.Point(firstVertex); + } else if (targetView) { + // TODO: the source anchor reference is not a point, how to deal with this? + sourceAnchorRef = this.targetMagnet || targetView.el; + } else { + sourceAnchorRef = new g.Point(targetDef); + } + sourceAnchor = this.getAnchor(sourceDef.anchor, sourceView, sourceMagnet, sourceAnchorRef, 'source'); + } else { + sourceAnchor = new g.Point(sourceDef); + } + + // Anchor Target + var targetAnchor; + if (targetView) { + targetMagnet = (this.targetMagnet || targetView.el); + var targetAnchorRef = new g.Point(lastVertex || sourceAnchor); + targetAnchor = this.getAnchor(targetDef.anchor, targetView, targetMagnet, targetAnchorRef, 'target'); + } else { + targetAnchor = new g.Point(targetDef); + } + + // Con + return { + source: sourceAnchor, + target: targetAnchor + } + }, + + findConnectionPoints: function(route, sourceAnchor, targetAnchor) { + + var firstWaypoint = route[0]; + var lastWaypoint = route[route.length - 1]; + var model = this.model; + var sourceDef = model.get('source'); + var targetDef = model.get('target'); + var sourceView = this.sourceView; + var targetView = this.targetView; + var paperOptions = this.paper.options; + var sourceMagnet, targetMagnet; + + // Connection Point Source + var sourcePoint; + if (sourceView) { + sourceMagnet = (this.sourceMagnet || sourceView.el); + var sourceConnectionPointDef = sourceDef.connectionPoint || paperOptions.defaultConnectionPoint; + var sourcePointRef = firstWaypoint || targetAnchor; + var sourceLine = new g.Line(sourcePointRef, sourceAnchor); + sourcePoint = this.getConnectionPoint(sourceConnectionPointDef, sourceView, sourceMagnet, sourceLine, 'source'); + } else { + sourcePoint = sourceAnchor; + } + // Connection Point Target + var targetPoint; + if (targetView) { + targetMagnet = (this.targetMagnet || targetView.el); + var targetConnectionPointDef = targetDef.connectionPoint || paperOptions.defaultConnectionPoint; + var targetPointRef = lastWaypoint || sourceAnchor; + var targetLine = new g.Line(targetPointRef, targetAnchor); + targetPoint = this.getConnectionPoint(targetConnectionPointDef, targetView, targetMagnet, targetLine, 'target'); + } else { + targetPoint = targetAnchor; + } + + return { + source: sourcePoint, + target: targetPoint + } + }, + + getAnchor: function(anchorDef, cellView, magnet, ref, endType) { + + if (!anchorDef) { + var paperOptions = this.paper.options; + if (paperOptions.perpendicularLinks || this.options.perpendicular) { + // Backwards compatibility + // If `perpendicularLinks` flag is set on the paper and there are vertices + // on the link, then try to find a connection point that makes the link perpendicular + // even though the link won't point to the center of the targeted object. + anchorDef = { name: 'perpendicular' }; + } else { + anchorDef = paperOptions.defaultAnchor; + } + } + + if (!anchorDef) throw new Error('Anchor required.'); + var anchorFn; + if (typeof anchorDef === 'function') { + anchorFn = anchorDef; + } else { + var anchorName = anchorDef.name; + anchorFn = joint.anchors[anchorName]; + if (typeof anchorFn !== 'function') throw new Error('Unknown anchor: ' + anchorName); + } + var anchor = anchorFn.call(this, cellView, magnet, ref, anchorDef.args || {}, endType, this); + if (anchor) return anchor.round(this.decimalsRounding); + return new g.Point() + }, + + + getConnectionPoint: function(connectionPointDef, view, magnet, line, endType) { + + var connectionPoint; + var anchor = line.end; + // Backwards compatibility + var paperOptions = this.paper.options; + if (typeof paperOptions.linkConnectionPoint === 'function') { + connectionPoint = paperOptions.linkConnectionPoint(this, view, magnet, line.start, endType); + if (connectionPoint) return connectionPoint; + } + + if (!connectionPointDef) return anchor; + var connectionPointFn; + if (typeof connectionPointDef === 'function') { + connectionPointFn = connectionPointDef; + } else { + var connectionPointName = connectionPointDef.name; + connectionPointFn = joint.connectionPoints[connectionPointName]; + if (typeof connectionPointFn !== 'function') throw new Error('Unknown connection point: ' + connectionPointName); + } + connectionPoint = connectionPointFn.call(this, line, view, magnet, connectionPointDef.args || {}, endType, this); + if (connectionPoint) return connectionPoint.round(this.decimalsRounding); + return anchor; + }, + + _translateConnectionPoints: function(tx, ty) { + + var cache = this._markerCache; + + cache.sourcePoint.offset(tx, ty); + cache.targetPoint.offset(tx, ty); + this.sourcePoint.offset(tx, ty); + this.targetPoint.offset(tx, ty); + this.sourceAnchor.offset(tx, ty); + this.targetAnchor.offset(tx, ty); + }, + + // if label position is a number, normalize it to a position object + // this makes sure that label positions can be merged properly + _normalizeLabelPosition: function(labelPosition) { + + if (typeof labelPosition === 'number') return { distance: labelPosition, offset: null, args: null }; + return labelPosition; + }, + + updateLabelPositions: function() { + + if (!this._V.labels) return this; + + var path = this.path; + if (!path) return this; + + // This method assumes all the label nodes are stored in the `this._labelCache` hash table + // by their indices in the `this.get('labels')` array. This is done in the `renderLabels()` method. + + var model = this.model; + var labels = model.get('labels') || []; + if (!labels.length) return this; + + var builtinDefaultLabel = model._builtins.defaultLabel; + var builtinDefaultLabelPosition = builtinDefaultLabel.position; + + var defaultLabel = model._getDefaultLabel(); + var defaultLabelPosition = this._normalizeLabelPosition(defaultLabel.position); + + var defaultPosition = joint.util.merge({}, builtinDefaultLabelPosition, defaultLabelPosition); + + for (var idx = 0, n = labels.length; idx < n; idx++) { + + var label = labels[idx]; + var labelPosition = this._normalizeLabelPosition(label.position); + + var position = joint.util.merge({}, defaultPosition, labelPosition); + + var labelPoint = this.getLabelCoordinates(position); + this._labelCache[idx].attr('transform', 'translate(' + labelPoint.x + ', ' + labelPoint.y + ')'); + } + + return this; + }, + + updateToolsPosition: function() { + + if (!this._V.linkTools) return this; + + // Move the tools a bit to the target position but don't cover the `sourceArrowhead` marker. + // Note that the offset is hardcoded here. The offset should be always + // more than the `this.$('.marker-arrowhead[end="source"]')[0].bbox().width` but looking + // this up all the time would be slow. + + var scale = ''; + var offset = this.options.linkToolsOffset; + var connectionLength = this.getConnectionLength(); + + // Firefox returns connectionLength=NaN in odd cases (for bezier curves). + // In that case we won't update tools position at all. + if (!Number.isNaN(connectionLength)) { + + // If the link is too short, make the tools half the size and the offset twice as low. + if (connectionLength < this.options.shortLinkLength) { + scale = 'scale(.5)'; + offset /= 2; + } + + var toolPosition = this.getPointAtLength(offset); + + this._toolCache.attr('transform', 'translate(' + toolPosition.x + ', ' + toolPosition.y + ') ' + scale); + + if (this.options.doubleLinkTools && connectionLength >= this.options.longLinkLength) { + + var doubleLinkToolsOffset = this.options.doubleLinkToolsOffset || offset; + + toolPosition = this.getPointAtLength(connectionLength - doubleLinkToolsOffset); + this._tool2Cache.attr('transform', 'translate(' + toolPosition.x + ', ' + toolPosition.y + ') ' + scale); + this._tool2Cache.attr('visibility', 'visible'); + + } else if (this.options.doubleLinkTools) { + + this._tool2Cache.attr('visibility', 'hidden'); + } + } + + return this; + }, + + updateArrowheadMarkers: function() { + + if (!this._V.markerArrowheads) return this; + + // getting bbox of an element with `display="none"` in IE9 ends up with access violation + if ($.css(this._V.markerArrowheads.node, 'display') === 'none') return this; + + var sx = this.getConnectionLength() < this.options.shortLinkLength ? .5 : 1; + this._V.sourceArrowhead.scale(sx); + this._V.targetArrowhead.scale(sx); + + this._translateAndAutoOrientArrows(this._V.sourceArrowhead, this._V.targetArrowhead); + + return this; + }, + + // Returns a function observing changes on an end of the link. If a change happens and new end is a new model, + // it stops listening on the previous one and starts listening to the new one. + createWatcher: function(endType) { + + // create handler for specific end type (source|target). + var onModelChange = function(endModel, opt) { + this.onEndModelChange(endType, endModel, opt); + }; + + function watchEndModel(link, end) { + + end = end || {}; + + var endModel = null; + var previousEnd = link.previous(endType) || {}; + + if (previousEnd.id) { + this.stopListening(this.paper.getModelById(previousEnd.id), 'change', onModelChange); + } + + if (end.id) { + // If the observed model changes, it caches a new bbox and do the link update. + endModel = this.paper.getModelById(end.id); + this.listenTo(endModel, 'change', onModelChange); + } + + onModelChange.call(this, endModel, { cacheOnly: true }); + + return this; + } + + return watchEndModel; + }, + + onEndModelChange: function(endType, endModel, opt) { + + var doUpdate = !opt.cacheOnly; + var model = this.model; + var end = model.get(endType) || {}; + + if (endModel) { + + var selector = this.constructor.makeSelector(end); + var oppositeEndType = endType == 'source' ? 'target' : 'source'; + var oppositeEnd = model.get(oppositeEndType) || {}; + var endId = end.id; + var oppositeEndId = oppositeEnd.id; + var oppositeSelector = oppositeEndId && this.constructor.makeSelector(oppositeEnd); + + // Caching end models bounding boxes. + // If `opt.handleBy` equals the client-side ID of this link view and it is a loop link, then we already cached + // the bounding boxes in the previous turn (e.g. for loop link, the change:source event is followed + // by change:target and so on change:source, we already chached the bounding boxes of - the same - element). + if (opt.handleBy === this.cid && (endId === oppositeEndId) && selector == oppositeSelector) { + + // Source and target elements are identical. We're dealing with a loop link. We are handling `change` event for the + // second time now. There is no need to calculate bbox and find magnet element again. + // It was calculated already for opposite link end. + this[endType + 'View'] = this[oppositeEndType + 'View']; + this[endType + 'Magnet'] = this[oppositeEndType + 'Magnet']; + + } else if (opt.translateBy) { + // `opt.translateBy` optimizes the way we calculate bounding box of the source/target element. + // If `opt.translateBy` is an ID of the element that was originally translated. + + // Noop + + } else { + // The slowest path, source/target could have been rotated or resized or any attribute + // that affects the bounding box of the view might have been changed. + + var connectedModel = this.paper.model.getCell(endId); + if (!connectedModel) throw new Error('LinkView: invalid ' + endType + ' cell.'); + var connectedView = connectedModel.findView(this.paper); + if (connectedView) { + var connectedMagnet = connectedView.getMagnetFromLinkEnd(end); + if (connectedMagnet === connectedView.el) connectedMagnet = null; + this[endType + 'View'] = connectedView; + this[endType + 'Magnet'] = connectedMagnet; + } else { + // the view is not rendered yet + this[endType + 'View'] = this[endType + 'Magnet'] = null; + } + } + + if (opt.handleBy === this.cid && opt.translateBy && + model.isEmbeddedIn(endModel) && + !joint.util.isEmpty(model.get('vertices'))) { + // Loop link whose element was translated and that has vertices (that need to be translated with + // the parent in which my element is embedded). + // If the link is embedded, has a loop and vertices and the end model + // has been translated, do not update yet. There are vertices still to be updated (change:vertices + // event will come in the next turn). + doUpdate = false; + } + + if (!this.updatePostponed && oppositeEndId) { + // The update was not postponed (that can happen e.g. on the first change event) and the opposite + // end is a model (opposite end is the opposite end of the link we're just updating, e.g. if + // we're reacting on change:source event, the oppositeEnd is the target model). + + var oppositeEndModel = this.paper.getModelById(oppositeEndId); + + // Passing `handleBy` flag via event option. + // Note that if we are listening to the same model for event 'change' twice. + // The same event will be handled by this method also twice. + if (end.id === oppositeEnd.id) { + // We're dealing with a loop link. Tell the handlers in the next turn that they should update + // the link instead of me. (We know for sure there will be a next turn because + // loop links react on at least two events: change on the source model followed by a change on + // the target model). + opt.handleBy = this.cid; + } + + if (opt.handleBy === this.cid || (opt.translateBy && oppositeEndModel.isEmbeddedIn(opt.translateBy))) { + + // Here are two options: + // - Source and target are connected to the same model (not necessarily the same port). + // - Both end models are translated by the same ancestor. We know that opposite end + // model will be translated in the next turn as well. + // In both situations there will be more changes on the model that trigger an + // update. So there is no need to update the linkView yet. + this.updatePostponed = true; + doUpdate = false; + } + } + + } else { + + // the link end is a point ~ rect 1x1 + this[endType + 'View'] = this[endType + 'Magnet'] = null; + } + + if (doUpdate) { + this.update(model, null, opt); + } + }, + + _translateAndAutoOrientArrows: function(sourceArrow, targetArrow) { + + // Make the markers "point" to their sticky points being auto-oriented towards + // `targetPosition`/`sourcePosition`. And do so only if there is a markup for them. + var route = joint.util.toArray(this.route); + if (sourceArrow) { + sourceArrow.translateAndAutoOrient( + this.sourcePoint, + route[0] || this.targetPoint, + this.paper.viewport + ); + } + + if (targetArrow) { + targetArrow.translateAndAutoOrient( + this.targetPoint, + route[route.length - 1] || this.sourcePoint, + this.paper.viewport + ); + } + }, + + _getDefaultLabelPositionArgs: function() { + + var defaultLabel = this.model._getDefaultLabel(); + var defaultLabelPosition = defaultLabel.position || {}; + return defaultLabelPosition.args; + }, + + _getLabelPositionArgs: function(idx) { + + var labelPosition = this.model.label(idx).position || {}; + return labelPosition.args; + }, + + // merge default label position args into label position args + // keep `undefined` or `null` because `{}` means something else + _mergeLabelPositionArgs: function(labelPositionArgs, defaultLabelPositionArgs) { + + if (labelPositionArgs === null) return null; + if (labelPositionArgs === undefined) { + + if (defaultLabelPositionArgs === null) return null; + return defaultLabelPositionArgs; + } + + return joint.util.merge({}, defaultLabelPositionArgs, labelPositionArgs); + }, + + // Add default label at given position at end of `labels` array. + // Assigns relative coordinates by default. + // `opt.absoluteDistance` forces absolute coordinates. + // `opt.reverseDistance` forces reverse absolute coordinates (if absoluteDistance = true). + // `opt.absoluteOffset` forces absolute coordinates for offset. + addLabel: function(x, y, opt) { + + // accept input in form `{ x, y }, opt` or `x, y, opt` + var isPointProvided = (typeof x !== 'number'); + var localX = isPointProvided ? x.x : x; + var localY = isPointProvided ? x.y : y; + var localOpt = isPointProvided ? y : opt; + + var defaultLabelPositionArgs = this._getDefaultLabelPositionArgs(); + var labelPositionArgs = localOpt; + var positionArgs = this._mergeLabelPositionArgs(labelPositionArgs, defaultLabelPositionArgs); + + var label = { position: this.getLabelPosition(localX, localY, positionArgs) }; + var idx = -1; + this.model.insertLabel(idx, label, localOpt); + return idx; + }, + + // Add a new vertex at calculated index to the `vertices` array. + addVertex: function(x, y, opt) { + + // accept input in form `{ x, y }, opt` or `x, y, opt` + var isPointProvided = (typeof x !== 'number'); + var localX = isPointProvided ? x.x : x; + var localY = isPointProvided ? x.y : y; + var localOpt = isPointProvided ? y : opt; + + var vertex = { x: localX, y: localY }; + var idx = this.getVertexIndex(localX, localY); + this.model.insertVertex(idx, vertex, localOpt); + return idx; + }, + + // Send a token (an SVG element, usually a circle) along the connection path. + // Example: `link.findView(paper).sendToken(V('circle', { r: 7, fill: 'green' }).node)` + // `opt.duration` is optional and is a time in milliseconds that the token travels from the source to the target of the link. Default is `1000`. + // `opt.directon` is optional and it determines whether the token goes from source to target or other way round (`reverse`) + // `opt.connection` is an optional selector to the connection path. + // `callback` is optional and is a function to be called once the token reaches the target. + sendToken: function(token, opt, callback) { + + function onAnimationEnd(vToken, callback) { + return function() { + vToken.remove(); + if (typeof callback === 'function') { + callback(); + } + }; + } + + var duration, isReversed, selector; + if (joint.util.isObject(opt)) { + duration = opt.duration; + isReversed = (opt.direction === 'reverse'); + selector = opt.connection; + } else { + // Backwards compatibility + duration = opt; + isReversed = false; + selector = null; + } + + duration = duration || 1000; + + var animationAttributes = { + dur: duration + 'ms', + repeatCount: 1, + calcMode: 'linear', + fill: 'freeze' + }; + + if (isReversed) { + animationAttributes.keyPoints = '1;0'; + animationAttributes.keyTimes = '0;1'; + } + + var vToken = V(token); + var connection; + if (typeof selector === 'string') { + // Use custom connection path. + connection = this.findBySelector(selector, this.el, this.selectors)[0]; + } else { + // Select connection path automatically. + var cache = this._V; + connection = (cache.connection) ? cache.connection.node : this.el.querySelector('path'); + } + + if (!(connection instanceof SVGPathElement)) { + throw new Error('dia.LinkView: token animation requires a valid connection path.'); + } + + vToken + .appendTo(this.paper.viewport) + .animateAlongPath(animationAttributes, connection); + + setTimeout(onAnimationEnd(vToken, callback), duration); + }, + + findRoute: function(vertices) { + + vertices || (vertices = []); + + var namespace = joint.routers; + var router = this.model.router(); + var defaultRouter = this.paper.options.defaultRouter; + + if (!router) { + if (defaultRouter) router = defaultRouter; + else return vertices.map(g.Point, g); // no router specified + } + + var routerFn = joint.util.isFunction(router) ? router : namespace[router.name]; + if (!joint.util.isFunction(routerFn)) { + throw new Error('dia.LinkView: unknown router: "' + router.name + '".'); + } + + var args = router.args || {}; + + var route = routerFn.call( + this, // context + vertices, // vertices + args, // options + this // linkView + ); + + if (!route) return vertices.map(g.Point, g); + return route; + }, + + // Return the `d` attribute value of the `` element representing the link + // between `source` and `target`. + findPath: function(route, sourcePoint, targetPoint) { + + var namespace = joint.connectors; + var connector = this.model.connector(); + var defaultConnector = this.paper.options.defaultConnector; + + if (!connector) { + connector = defaultConnector || {}; + } + + var connectorFn = joint.util.isFunction(connector) ? connector : namespace[connector.name]; + if (!joint.util.isFunction(connectorFn)) { + throw new Error('dia.LinkView: unknown connector: "' + connector.name + '".'); + } + + var args = joint.util.clone(connector.args || {}); + args.raw = true; // Request raw g.Path as the result. + + var path = connectorFn.call( + this, // context + sourcePoint, // start point + targetPoint, // end point + route, // vertices + args, // options + this // linkView + ); + + if (typeof path === 'string') { + // Backwards compatibility for connectors not supporting `raw` option. + path = new g.Path(V.normalizePathData(path)); + } + + return path; + }, + + // Public API. + // ----------- + + getConnection: function() { + + var path = this.path; + if (!path) return null; + + return path.clone(); + }, + + getSerializedConnection: function() { + + var path = this.path; + if (!path) return null; + + var metrics = this.metrics; + if (metrics.hasOwnProperty('data')) return metrics.data; + var data = path.serialize(); + metrics.data = data; + return data; + }, + + getConnectionSubdivisions: function() { + + var path = this.path; + if (!path) return null; + + var metrics = this.metrics; + if (metrics.hasOwnProperty('segmentSubdivisions')) return metrics.segmentSubdivisions; + var subdivisions = path.getSegmentSubdivisions(); + metrics.segmentSubdivisions = subdivisions; + return subdivisions; + }, + + getConnectionLength: function() { + + var path = this.path; + if (!path) return 0; + + var metrics = this.metrics; + if (metrics.hasOwnProperty('length')) return metrics.length; + var length = path.length({ segmentSubdivisions: this.getConnectionSubdivisions() }); + metrics.length = length; + return length; + }, + + getPointAtLength: function(length) { + + var path = this.path; + if (!path) return null; + + return path.pointAtLength(length, { segmentSubdivisions: this.getConnectionSubdivisions() }); + }, + + getPointAtRatio: function(ratio) { + + var path = this.path; + if (!path) return null; + + return path.pointAt(ratio, { segmentSubdivisions: this.getConnectionSubdivisions() }); + }, + + getTangentAtLength: function(length) { + + var path = this.path; + if (!path) return null; + + return path.tangentAtLength(length, { segmentSubdivisions: this.getConnectionSubdivisions() }); + }, + + getTangentAtRatio: function(ratio) { + + var path = this.path; + if (!path) return null; + + return path.tangentAt(ratio, { segmentSubdivisions: this.getConnectionSubdivisions() }); + }, + + getClosestPoint: function(point) { + + var path = this.path; + if (!path) return null; + + return path.closestPoint(point, { segmentSubdivisions: this.getConnectionSubdivisions() }); + }, + + getClosestPointLength: function(point) { + + var path = this.path; + if (!path) return null; + + return path.closestPointLength(point, { segmentSubdivisions: this.getConnectionSubdivisions() }); + }, + + getClosestPointRatio: function(point) { + + var path = this.path; + if (!path) return null; + + return path.closestPointNormalizedLength(point, { segmentSubdivisions: this.getConnectionSubdivisions() }); + }, + + // accepts options `absoluteDistance: boolean`, `reverseDistance: boolean`, `absoluteOffset: boolean` + // to move beyond connection endpoints, absoluteOffset has to be set + getLabelPosition: function(x, y, opt) { + + var position = {}; + + var localOpt = opt || {}; + if (opt) position.args = opt; + + var isDistanceRelative = !localOpt.absoluteDistance; // relative by default + var isDistanceAbsoluteReverse = (localOpt.absoluteDistance && localOpt.reverseDistance); // non-reverse by default + var isOffsetAbsolute = localOpt.absoluteOffset; // offset is non-absolute by default + + var path = this.path; + var pathOpt = { segmentSubdivisions: this.getConnectionSubdivisions() }; + + var labelPoint = new g.Point(x, y); + var t = path.closestPointT(labelPoint, pathOpt); + + // GET DISTANCE: + + var labelDistance = path.lengthAtT(t, pathOpt); + if (isDistanceRelative) labelDistance = (labelDistance / this.getConnectionLength()) || 0; // fix to prevent NaN for 0 length + if (isDistanceAbsoluteReverse) labelDistance = (-1 * (this.getConnectionLength() - labelDistance)) || 1; // fix for end point (-0 => 1) + + position.distance = labelDistance; + + // GET OFFSET: + // use absolute offset if: + // - opt.absoluteOffset is true, + // - opt.absoluteOffset is not true but there is no tangent + + var tangent; + if (!isOffsetAbsolute) tangent = path.tangentAtT(t); + + var labelOffset; + if (tangent) { + labelOffset = tangent.pointOffset(labelPoint); + + } else { + var closestPoint = path.pointAtT(t); + var labelOffsetDiff = labelPoint.difference(closestPoint); + labelOffset = { x: labelOffsetDiff.x, y: labelOffsetDiff.y }; + } + + position.offset = labelOffset; + + return position; + }, + + getLabelCoordinates: function(labelPosition) { + + var labelDistance; + if (typeof labelPosition === 'number') labelDistance = labelPosition; + else if (typeof labelPosition.distance === 'number') labelDistance = labelPosition.distance; + else throw new Error('dia.LinkView: invalid label position distance.'); + + var isDistanceRelative = ((labelDistance > 0) && (labelDistance <= 1)); + + var labelOffset = 0; + var labelOffsetCoordinates = { x: 0, y: 0 }; + if (labelPosition.offset) { + var positionOffset = labelPosition.offset; + if (typeof positionOffset === 'number') labelOffset = positionOffset; + if (positionOffset.x) labelOffsetCoordinates.x = positionOffset.x; + if (positionOffset.y) labelOffsetCoordinates.y = positionOffset.y; + } + + var isOffsetAbsolute = ((labelOffsetCoordinates.x !== 0) || (labelOffsetCoordinates.y !== 0) || labelOffset === 0); + + var path = this.path; + var pathOpt = { segmentSubdivisions: this.getConnectionSubdivisions() }; + + var distance = isDistanceRelative ? (labelDistance * this.getConnectionLength()) : labelDistance; + + var point; + + if (isOffsetAbsolute) { + point = path.pointAtLength(distance, pathOpt); + point.offset(labelOffsetCoordinates); + + } else { + var tangent = path.tangentAtLength(distance, pathOpt); + + if (tangent) { + tangent.rotate(tangent.start, -90); + tangent.setLength(labelOffset); + point = tangent.end; + + } else { + // fallback - the connection has zero length + point = path.start; + } + } + + return point; + }, + + getVertexIndex: function(x, y) { + + var model = this.model; + var vertices = model.vertices(); + + var vertexLength = this.getClosestPointLength(new g.Point(x, y)); + + var idx = 0; + for (var n = vertices.length; idx < n; idx++) { + var currentVertex = vertices[idx]; + var currentVertexLength = this.getClosestPointLength(currentVertex); + if (vertexLength < currentVertexLength) break; + } + + return idx; + }, + + // Interaction. The controller part. + // --------------------------------- + + pointerdblclick: function(evt, x, y) { + + joint.dia.CellView.prototype.pointerdblclick.apply(this, arguments); + this.notify('link:pointerdblclick', evt, x, y); + }, + + pointerclick: function(evt, x, y) { + + joint.dia.CellView.prototype.pointerclick.apply(this, arguments); + this.notify('link:pointerclick', evt, x, y); + }, + + contextmenu: function(evt, x, y) { + + joint.dia.CellView.prototype.contextmenu.apply(this, arguments); + this.notify('link:contextmenu', evt, x, y); + }, + + pointerdown: function(evt, x, y) { + + joint.dia.CellView.prototype.pointerdown.apply(this, arguments); + this.notify('link:pointerdown', evt, x, y); + + // Backwards compatibility for the default markup + var className = evt.target.getAttribute('class'); + switch (className) { + + case 'marker-vertex': + this.dragVertexStart(evt, x, y); + return; + + case 'marker-vertex-remove': + case 'marker-vertex-remove-area': + this.dragVertexRemoveStart(evt, x, y); + return; + + case 'marker-arrowhead': + this.dragArrowheadStart(evt, x, y); + return; + + case 'connection': + case 'connection-wrap': + this.dragConnectionStart(evt, x, y); + return; + + case 'marker-source': + case 'marker-target': + return; + } + + this.dragStart(evt, x, y); + }, + + pointermove: function(evt, x, y) { + + // Backwards compatibility + var dragData = this._dragData; + if (dragData) this.eventData(evt, dragData); + + var data = this.eventData(evt); + switch (data.action) { + + case 'vertex-move': + this.dragVertex(evt, x, y); + break; + + case 'label-move': + this.dragLabel(evt, x, y); + break; + + case 'arrowhead-move': + this.dragArrowhead(evt, x, y); + break; + + case 'move': + this.drag(evt, x, y); + break; + } + + // Backwards compatibility + if (dragData) joint.util.assign(dragData, this.eventData(evt)); + + joint.dia.CellView.prototype.pointermove.apply(this, arguments); + this.notify('link:pointermove', evt, x, y); + }, + + pointerup: function(evt, x, y) { + + // Backwards compatibility + var dragData = this._dragData; + if (dragData) { + this.eventData(evt, dragData); + this._dragData = null; + } + + var data = this.eventData(evt); + switch (data.action) { + + case 'vertex-move': + this.dragVertexEnd(evt, x, y); + break; + + case 'label-move': + this.dragLabelEnd(evt, x, y); + break; + + case 'arrowhead-move': + this.dragArrowheadEnd(evt, x, y); + break; + + case 'move': + this.dragEnd(evt, x, y); + } + + this.notify('link:pointerup', evt, x, y); + joint.dia.CellView.prototype.pointerup.apply(this, arguments); + }, + + mouseover: function(evt) { + + joint.dia.CellView.prototype.mouseover.apply(this, arguments); + this.notify('link:mouseover', evt); + }, + + mouseout: function(evt) { + + joint.dia.CellView.prototype.mouseout.apply(this, arguments); + this.notify('link:mouseout', evt); + }, + + mouseenter: function(evt) { + + joint.dia.CellView.prototype.mouseenter.apply(this, arguments); + this.notify('link:mouseenter', evt); + }, + + mouseleave: function(evt) { + + joint.dia.CellView.prototype.mouseleave.apply(this, arguments); + this.notify('link:mouseleave', evt); + }, + + mousewheel: function(evt, x, y, delta) { + + joint.dia.CellView.prototype.mousewheel.apply(this, arguments); + this.notify('link:mousewheel', evt, x, y, delta); + }, + + onevent: function(evt, eventName, x, y) { + + // Backwards compatibility + var linkTool = V(evt.target).findParentByClass('link-tool', this.el); + if (linkTool) { + // No further action to be executed + evt.stopPropagation(); + + // Allow `interactive.useLinkTools=false` + if (this.can('useLinkTools')) { + if (eventName === 'remove') { + // Built-in remove event + this.model.remove({ ui: true }); + + } else { + // link:options and other custom events inside the link tools + this.notify(eventName, evt, x, y); + } + } + + } else { + joint.dia.CellView.prototype.onevent.apply(this, arguments); + } + }, + + onlabel: function(evt, x, y) { + + this.dragLabelStart(evt, x, y); + + var stopPropagation = this.eventData(evt).stopPropagation; + if (stopPropagation) evt.stopPropagation(); + }, + + // Drag Start Handlers + + dragConnectionStart: function(evt, x, y) { + + if (!this.can('vertexAdd')) return; + + // Store the index at which the new vertex has just been placed. + // We'll be update the very same vertex position in `pointermove()`. + var vertexIdx = this.addVertex({ x: x, y: y }, { ui: true }); + this.eventData(evt, { + action: 'vertex-move', + vertexIdx: vertexIdx + }); + }, + + dragLabelStart: function(evt, x, y) { + + if (!this.can('labelMove')) { + // Backwards compatibility: + // If labels can't be dragged no default action is triggered. + this.eventData(evt, { stopPropagation: true }); + return; + } + + var labelNode = evt.currentTarget; + var labelIdx = parseInt(labelNode.getAttribute('label-idx'), 10); + + var defaultLabelPositionArgs = this._getDefaultLabelPositionArgs(); + var labelPositionArgs = this._getLabelPositionArgs(labelIdx); + var positionArgs = this._mergeLabelPositionArgs(labelPositionArgs, defaultLabelPositionArgs); + + this.eventData(evt, { + action: 'label-move', + labelIdx: labelIdx, + positionArgs: positionArgs, + stopPropagation: true + }); + + this.paper.delegateDragEvents(this, evt.data); + }, + + dragVertexStart: function(evt, x, y) { + + if (!this.can('vertexMove')) return; + + var vertexNode = evt.target; + var vertexIdx = parseInt(vertexNode.getAttribute('idx'), 10); + this.eventData(evt, { + action: 'vertex-move', + vertexIdx: vertexIdx + }); + }, + + dragVertexRemoveStart: function(evt, x, y) { + + if (!this.can('vertexRemove')) return; + + var removeNode = evt.target; + var vertexIdx = parseInt(removeNode.getAttribute('idx'), 10); + this.model.removeVertex(vertexIdx); + }, + + dragArrowheadStart: function(evt, x, y) { + + if (!this.can('arrowheadMove')) return; + + var arrowheadNode = evt.target; + var arrowheadType = arrowheadNode.getAttribute('end'); + var data = this.startArrowheadMove(arrowheadType, { ignoreBackwardsCompatibility: true }); + + this.eventData(evt, data); + }, + + dragStart: function(evt, x, y) { + + if (!this.can('linkMove')) return; + + this.eventData(evt, { + action: 'move', + dx: x, + dy: y + }); + }, + + // Drag Handlers + + dragLabel: function(evt, x, y) { + + var data = this.eventData(evt); + var label = { position: this.getLabelPosition(x, y, data.positionArgs) }; + this.model.label(data.labelIdx, label); + }, + + dragVertex: function(evt, x, y) { + + var data = this.eventData(evt); + this.model.vertex(data.vertexIdx, { x: x, y: y }, { ui: true }); + }, + + dragArrowhead: function(evt, x, y) { + + var data = this.eventData(evt); + + if (this.paper.options.snapLinks) { + + this._snapArrowhead(x, y, data); + + } else { + // Touchmove event's target is not reflecting the element under the coordinates as mousemove does. + // It holds the element when a touchstart triggered. + var target = (evt.type === 'mousemove') + ? evt.target + : document.elementFromPoint(evt.clientX, evt.clientY); + + this._connectArrowhead(target, x, y, data); + } + }, + + drag: function(evt, x, y) { + + var data = this.eventData(evt); + this.model.translate(x - data.dx, y - data.dy, { ui: true }); + this.eventData(evt, { + dx: x, + dy: y + }); + }, + + // Drag End Handlers + + dragLabelEnd: function() { + // noop + }, + + dragVertexEnd: function() { + // noop + }, + + dragArrowheadEnd: function(evt, x, y) { + + var data = this.eventData(evt); + var paper = this.paper; + + if (paper.options.snapLinks) { + this._snapArrowheadEnd(data); + } else { + this._connectArrowheadEnd(data, x, y); + } + + if (!paper.linkAllowed(this)) { + // If the changed link is not allowed, revert to its previous state. + this._disallow(data); + } else { + this._finishEmbedding(data); + this._notifyConnectEvent(data, evt); + } + + this._afterArrowheadMove(data); + + // mouseleave event is not triggered due to changing pointer-events to `none`. + if (!this.vel.contains(evt.target)) { + this.mouseleave(evt); + } + }, + + dragEnd: function() { + // noop + }, + + _disallow: function(data) { + + switch (data.whenNotAllowed) { + + case 'remove': + this.model.remove({ ui: true }); + break; + + case 'revert': + default: + this.model.set(data.arrowhead, data.initialEnd, { ui: true }); + break; + } + }, + + _finishEmbedding: function(data) { + + // Reparent the link if embedding is enabled + if (this.paper.options.embeddingMode && this.model.reparent()) { + // Make sure we don't reverse to the original 'z' index (see afterArrowheadMove()). + data.z = null; + } + }, + + _notifyConnectEvent: function(data, evt) { + + var arrowhead = data.arrowhead; + var initialEnd = data.initialEnd; + var currentEnd = this.model.prop(arrowhead); + var endChanged = currentEnd && !joint.dia.Link.endsEqual(initialEnd, currentEnd); + if (endChanged) { + var paper = this.paper; + if (initialEnd.id) { + this.notify('link:disconnect', evt, paper.findViewByModel(initialEnd.id), data.initialMagnet, arrowhead); + } + if (currentEnd.id) { + this.notify('link:connect', evt, paper.findViewByModel(currentEnd.id), data.magnetUnderPointer, arrowhead); + } + } + }, + + _snapArrowhead: function(x, y, data) { + + // checking view in close area of the pointer + + var r = this.paper.options.snapLinks.radius || 50; + var viewsInArea = this.paper.findViewsInArea({ x: x - r, y: y - r, width: 2 * r, height: 2 * r }); + + if (data.closestView) { + data.closestView.unhighlight(data.closestMagnet, { + connecting: true, + snapping: true + }); + } + data.closestView = data.closestMagnet = null; + + var distance; + var minDistance = Number.MAX_VALUE; + var pointer = g.point(x, y); + var paper = this.paper; + + viewsInArea.forEach(function(view) { + + // skip connecting to the element in case '.': { magnet: false } attribute present + if (view.el.getAttribute('magnet') !== 'false') { + + // find distance from the center of the model to pointer coordinates + distance = view.model.getBBox().center().distance(pointer); + + // the connection is looked up in a circle area by `distance < r` + if (distance < r && distance < minDistance) { + + if (paper.options.validateConnection.apply( + paper, data.validateConnectionArgs(view, null) + )) { + minDistance = distance; + data.closestView = view; + data.closestMagnet = view.el; + } + } + } + + view.$('[magnet]').each(function(index, magnet) { + + var bbox = view.getNodeBBox(magnet); + + distance = pointer.distance({ + x: bbox.x + bbox.width / 2, + y: bbox.y + bbox.height / 2 + }); + + if (distance < r && distance < minDistance) { + + if (paper.options.validateConnection.apply( + paper, data.validateConnectionArgs(view, magnet) + )) { + minDistance = distance; + data.closestView = view; + data.closestMagnet = magnet; + } + } + + }.bind(this)); + + }, this); + + var end; + var closestView = data.closestView; + var closestMagnet = data.closestMagnet; + var endType = data.arrowhead; + if (closestView) { + closestView.highlight(closestMagnet, { + connecting: true, + snapping: true + }); + end = closestView.getLinkEnd(closestMagnet, x, y, this.model, endType); + } else { + end = { x: x, y: y }; + } + + this.model.set(endType, end || { x: x, y: y }, { ui: true }); + }, + + _snapArrowheadEnd: function(data) { + + // Finish off link snapping. + // Everything except view unhighlighting was already done on pointermove. + var closestView = data.closestView; + var closestMagnet = data.closestMagnet; + if (closestView && closestMagnet) { + + closestView.unhighlight(closestMagnet, { connecting: true, snapping: true }); + data.magnetUnderPointer = closestView.findMagnet(closestMagnet); + } + + data.closestView = data.closestMagnet = null; + }, + + _connectArrowhead: function(target, x, y, data) { + + // checking views right under the pointer + + if (data.eventTarget !== target) { + // Unhighlight the previous view under pointer if there was one. + if (data.magnetUnderPointer) { + data.viewUnderPointer.unhighlight(data.magnetUnderPointer, { + connecting: true + }); + } + + data.viewUnderPointer = this.paper.findView(target); + if (data.viewUnderPointer) { + // If we found a view that is under the pointer, we need to find the closest + // magnet based on the real target element of the event. + data.magnetUnderPointer = data.viewUnderPointer.findMagnet(target); + + if (data.magnetUnderPointer && this.paper.options.validateConnection.apply( + this.paper, + data.validateConnectionArgs(data.viewUnderPointer, data.magnetUnderPointer) + )) { + // If there was no magnet found, do not highlight anything and assume there + // is no view under pointer we're interested in reconnecting to. + // This can only happen if the overall element has the attribute `'.': { magnet: false }`. + if (data.magnetUnderPointer) { + data.viewUnderPointer.highlight(data.magnetUnderPointer, { + connecting: true + }); + } + } else { + // This type of connection is not valid. Disregard this magnet. + data.magnetUnderPointer = null; + } + } else { + // Make sure we'll unset previous magnet. + data.magnetUnderPointer = null; + } + } + + data.eventTarget = target; + + this.model.set(data.arrowhead, { x: x, y: y }, { ui: true }); + }, + + _connectArrowheadEnd: function(data, x, y) { + + var view = data.viewUnderPointer; + var magnet = data.magnetUnderPointer; + if (!magnet || !view) return; + + view.unhighlight(magnet, { connecting: true }); + + var endType = data.arrowhead; + var end = view.getLinkEnd(magnet, x, y, this.model, endType); + this.model.set(endType, end, { ui: true }); + }, + + _beforeArrowheadMove: function(data) { + + data.z = this.model.get('z'); + this.model.toFront(); + + // Let the pointer propagate throught the link view elements so that + // the `evt.target` is another element under the pointer, not the link itself. + this.el.style.pointerEvents = 'none'; + + if (this.paper.options.markAvailable) { + this._markAvailableMagnets(data); + } + }, + + _afterArrowheadMove: function(data) { + + if (data.z !== null) { + this.model.set('z', data.z, { ui: true }); + data.z = null; + } + + // Put `pointer-events` back to its original value. See `startArrowheadMove()` for explanation. + // Value `auto` doesn't work in IE9. We force to use `visiblePainted` instead. + // See `https://developer.mozilla.org/en-US/docs/Web/CSS/pointer-events`. + this.el.style.pointerEvents = 'visiblePainted'; + + if (this.paper.options.markAvailable) { + this._unmarkAvailableMagnets(data); + } + }, + + _createValidateConnectionArgs: function(arrowhead) { + // It makes sure the arguments for validateConnection have the following form: + // (source view, source magnet, target view, target magnet and link view) + var args = []; + + args[4] = arrowhead; + args[5] = this; + + var oppositeArrowhead; + var i = 0; + var j = 0; + + if (arrowhead === 'source') { + i = 2; + oppositeArrowhead = 'target'; + } else { + j = 2; + oppositeArrowhead = 'source'; + } + + var end = this.model.get(oppositeArrowhead); + + if (end.id) { + args[i] = this.paper.findViewByModel(end.id); + args[i + 1] = end.selector && args[i].el.querySelector(end.selector); + } + + function validateConnectionArgs(cellView, magnet) { + args[j] = cellView; + args[j + 1] = cellView.el === magnet ? undefined : magnet; + return args; + } + + return validateConnectionArgs; + }, + + _markAvailableMagnets: function(data) { + + function isMagnetAvailable(view, magnet) { + var paper = view.paper; + var validate = paper.options.validateConnection; + return validate.apply(paper, this.validateConnectionArgs(view, magnet)); + } + + var paper = this.paper; + var elements = paper.model.getElements(); + data.marked = {}; + + for (var i = 0, n = elements.length; i < n; i++) { + var view = elements[i].findView(paper); + + if (!view) { + continue; + } + + var magnets = Array.prototype.slice.call(view.el.querySelectorAll('[magnet]')); + if (view.el.getAttribute('magnet') !== 'false') { + // Element wrapping group is also a magnet + magnets.push(view.el); + } + + var availableMagnets = magnets.filter(isMagnetAvailable.bind(data, view)); + + if (availableMagnets.length > 0) { + // highlight all available magnets + for (var j = 0, m = availableMagnets.length; j < m; j++) { + view.highlight(availableMagnets[j], { magnetAvailability: true }); + } + // highlight the entire view + view.highlight(null, { elementAvailability: true }); + + data.marked[view.model.id] = availableMagnets; + } + } + }, + + _unmarkAvailableMagnets: function(data) { + + var markedKeys = Object.keys(data.marked); + var id; + var markedMagnets; + + for (var i = 0, n = markedKeys.length; i < n; i++) { + id = markedKeys[i]; + markedMagnets = data.marked[id]; + + var view = this.paper.findViewByModel(id); + if (view) { + for (var j = 0, m = markedMagnets.length; j < m; j++) { + view.unhighlight(markedMagnets[j], { magnetAvailability: true }); + } + view.unhighlight(null, { elementAvailability: true }); + } + } + + data.marked = null; + }, + + startArrowheadMove: function(end, opt) { + + opt || (opt = {}); + + // Allow to delegate events from an another view to this linkView in order to trigger arrowhead + // move without need to click on the actual arrowhead dom element. + var data = { + action: 'arrowhead-move', + arrowhead: end, + whenNotAllowed: opt.whenNotAllowed || 'revert', + initialMagnet: this[end + 'Magnet'] || (this[end + 'View'] ? this[end + 'View'].el : null), + initialEnd: joint.util.clone(this.model.get(end)), + validateConnectionArgs: this._createValidateConnectionArgs(end) + }; + + this._beforeArrowheadMove(data); + + if (opt.ignoreBackwardsCompatibility !== true) { + this._dragData = data; + } + + return data; + } +}, { + + makeSelector: function(end) { + + var selector = ''; + // `port` has a higher precendence over `selector`. This is because the selector to the magnet + // might change while the name of the port can stay the same. + if (end.port) { + selector += '[port="' + end.port + '"]'; + } else if (end.selector) { + selector += end.selector; + } + + return selector; + } + +}); + + +Object.defineProperty(joint.dia.LinkView.prototype, 'sourceBBox', { + + enumerable: true, + + get: function() { + var sourceView = this.sourceView; + var sourceMagnet = this.sourceMagnet; + if (sourceView) { + if (!sourceMagnet) sourceMagnet = sourceView.el; + return sourceView.getNodeBBox(sourceMagnet); + } + var sourceDef = this.model.source(); + return new g.Rect(sourceDef.x, sourceDef.y, 1, 1); + } + +}); + +Object.defineProperty(joint.dia.LinkView.prototype, 'targetBBox', { + + enumerable: true, + + get: function() { + var targetView = this.targetView; + var targetMagnet = this.targetMagnet; + if (targetView) { + if (!targetMagnet) targetMagnet = targetView.el; + return targetView.getNodeBBox(targetMagnet); + } + var targetDef = this.model.target(); + return new g.Rect(targetDef.x, targetDef.y, 1, 1); + } +}); + + +joint.dia.Paper = joint.mvc.View.extend({ + + className: 'paper', + + options: { + + width: 800, + height: 600, + origin: { x: 0, y: 0 }, // x,y coordinates in top-left corner + gridSize: 1, + + // Whether or not to draw the grid lines on the paper's DOM element. + // e.g drawGrid: true, drawGrid: { color: 'red', thickness: 2 } + drawGrid: false, + + // Whether or not to draw the background on the paper's DOM element. + // e.g. background: { color: 'lightblue', image: '/paper-background.png', repeat: 'flip-xy' } + background: false, + + perpendicularLinks: false, + elementView: joint.dia.ElementView, + linkView: joint.dia.LinkView, + snapLinks: false, // false, true, { radius: value } + + // When set to FALSE, an element may not have more than 1 link with the same source and target element. + multiLinks: true, + + // For adding custom guard logic. + guard: function(evt, view) { + + // FALSE means the event isn't guarded. + return false; + }, + + highlighting: { + 'default': { + name: 'stroke', + options: { + padding: 3 + } + }, + magnetAvailability: { + name: 'addClass', + options: { + className: 'available-magnet' + } + }, + elementAvailability: { + name: 'addClass', + options: { + className: 'available-cell' + } + } + }, + + // Prevent the default context menu from being displayed. + preventContextMenu: true, + + // Prevent the default action for blank:pointer. + preventDefaultBlankAction: true, + + // Restrict the translation of elements by given bounding box. + // Option accepts a boolean: + // true - the translation is restricted to the paper area + // false - no restrictions + // A method: + // restrictTranslate: function(elementView) { + // var parentId = elementView.model.get('parent'); + // return parentId && this.model.getCell(parentId).getBBox(); + // }, + // Or a bounding box: + // restrictTranslate: { x: 10, y: 10, width: 790, height: 590 } + restrictTranslate: false, + + // Marks all available magnets with 'available-magnet' class name and all available cells with + // 'available-cell' class name. Marks them when dragging a link is started and unmark + // when the dragging is stopped. + markAvailable: false, + + // Defines what link model is added to the graph after an user clicks on an active magnet. + // Value could be the Backbone.model or a function returning the Backbone.model + // defaultLink: function(elementView, magnet) { return condition ? new customLink1() : new customLink2() } + defaultLink: new joint.dia.Link, + + // A connector that is used by links with no connector defined on the model. + // e.g. { name: 'rounded', args: { radius: 5 }} or a function + defaultConnector: { name: 'normal' }, + + // A router that is used by links with no router defined on the model. + // e.g. { name: 'oneSide', args: { padding: 10 }} or a function + defaultRouter: { name: 'normal' }, + + defaultAnchor: { name: 'center' }, + + defaultConnectionPoint: { name: 'bbox' }, + + /* CONNECTING */ + + connectionStrategy: null, + + // Check whether to add a new link to the graph when user clicks on an a magnet. + validateMagnet: function(cellView, magnet) { + return magnet.getAttribute('magnet') !== 'passive'; + }, + + // Check whether to allow or disallow the link connection while an arrowhead end (source/target) + // being changed. + validateConnection: function(cellViewS, magnetS, cellViewT, magnetT, end, linkView) { + return (end === 'target' ? cellViewT : cellViewS) instanceof joint.dia.ElementView; + }, + + /* EMBEDDING */ + + // Enables embedding. Reparents the dragged element with elements under it and makes sure that + // all links and elements are visible taken the level of embedding into account. + embeddingMode: false, + + // Check whether to allow or disallow the element embedding while an element being translated. + validateEmbedding: function(childView, parentView) { + // by default all elements can be in relation child-parent + return true; + }, + + // Determines the way how a cell finds a suitable parent when it's dragged over the paper. + // The cell with the highest z-index (visually on the top) will be chosen. + findParentBy: 'bbox', // 'bbox'|'center'|'origin'|'corner'|'topRight'|'bottomLeft' + + // If enabled only the element on the very front is taken into account for the embedding. + // If disabled the elements under the dragged view are tested one by one + // (from front to back) until a valid parent found. + frontParentOnly: true, + + // Interactive flags. See online docs for the complete list of interactive flags. + interactive: { + labelMove: false + }, + + // When set to true the links can be pinned to the paper. + // i.e. link source/target can be a point e.g. link.get('source') ==> { x: 100, y: 100 }; + linkPinning: true, + + // Custom validation after an interaction with a link ends. + // Recognizes a function. If `false` is returned, the link is disallowed (removed or reverted) + // (linkView, paper) => boolean + allowLink: null, + + // Allowed number of mousemove events after which the pointerclick event will be still triggered. + clickThreshold: 0, + + // Number of required mousemove events before the first pointermove event will be triggered. + moveThreshold: 0, + + // The namespace, where all the cell views are defined. + cellViewNamespace: joint.shapes, + + // The namespace, where all the cell views are defined. + highlighterNamespace: joint.highlighters + }, + + events: { + 'dblclick': 'pointerdblclick', + 'click': 'pointerclick', // triggered alongside pointerdown and pointerup if no movement + 'touchend': 'pointerclick', // triggered alongside pointerdown and pointerup if no movement + 'contextmenu': 'contextmenu', + 'mousedown': 'pointerdown', + 'touchstart': 'pointerdown', + 'mouseover': 'mouseover', + 'mouseout': 'mouseout', + 'mouseenter': 'mouseenter', + 'mouseleave': 'mouseleave', + 'mousewheel': 'mousewheel', + 'DOMMouseScroll': 'mousewheel', + 'mouseenter .joint-cell': 'mouseenter', + 'mouseleave .joint-cell': 'mouseleave', + 'mouseenter .joint-tools': 'mouseenter', + 'mouseleave .joint-tools': 'mouseleave', + 'mousedown .joint-cell [event]': 'onevent', // interaction with cell with `event` attribute set + 'touchstart .joint-cell [event]': 'onevent', + 'mousedown .joint-cell [magnet]': 'onmagnet', // interaction with cell with `magnet` attribute set + 'touchstart .joint-cell [magnet]': 'onmagnet', + 'mousedown .joint-link .label': 'onlabel', // interaction with link label + 'touchstart .joint-link .label': 'onlabel', + 'dragstart .joint-cell image': 'onImageDragStart' // firefox fix + }, + + documentEvents: { + 'mousemove': 'pointermove', + 'touchmove': 'pointermove', + 'mouseup': 'pointerup', + 'touchend': 'pointerup', + 'touchcancel': 'pointerup' + }, + + _highlights: {}, + + init: function() { + + joint.util.bindAll(this, 'pointerup'); + + var model = this.model = this.options.model || new joint.dia.Graph; + + this.setGrid(this.options.drawGrid); + this.cloneOptions(); + this.render(); + this.setDimensions(); + + this.listenTo(model, 'add', this.onCellAdded) + .listenTo(model, 'remove', this.removeView) + .listenTo(model, 'reset', this.resetViews) + .listenTo(model, 'sort', this._onSort) + .listenTo(model, 'batch:stop', this._onBatchStop); + + this.on('cell:highlight', this.onCellHighlight) + .on('cell:unhighlight', this.onCellUnhighlight) + .on('scale translate', this.update); + + // Hold the value when mouse has been moved: when mouse moved, no click event will be triggered. + this._mousemoved = 0; + // Hash of all cell views. + this._views = {}; + // Reference to the paper owner document + this.$document = $(this.el.ownerDocument); + }, + + cloneOptions: function() { + + var options = this.options; + + // This is a fix for the case where two papers share the same options. + // Changing origin.x for one paper would change the value of origin.x for the other. + // This prevents that behavior. + options.origin = joint.util.assign({}, options.origin); + options.defaultConnector = joint.util.assign({}, options.defaultConnector); + // Return the default highlighting options into the user specified options. + options.highlighting = joint.util.defaultsDeep( + {}, + options.highlighting, + this.constructor.prototype.options.highlighting + ); + }, + + render: function() { + + this.$el.empty(); + + this.svg = V('svg').attr({ width: '100%', height: '100%' }).node; + this.viewport = V('g').addClass(joint.util.addClassNamePrefix('viewport')).node; + this.defs = V('defs').node; + this.tools = V('g').addClass(joint.util.addClassNamePrefix('tools-container')).node; + // Append `` element to the SVG document. This is useful for filters and gradients. + // It's desired to have the defs defined before the viewport (e.g. to make a PDF document pick up defs properly). + V(this.svg).append([this.defs, this.viewport, this.tools]); + + this.$background = $('
').addClass(joint.util.addClassNamePrefix('paper-background')); + if (this.options.background) { + this.drawBackground(this.options.background); + } + + this.$grid = $('
').addClass(joint.util.addClassNamePrefix('paper-grid')); + if (this.options.drawGrid) { + this.drawGrid(); + } + + this.$el.append(this.$background, this.$grid, this.svg); + + return this; + }, + + update: function() { + + if (this.options.drawGrid) { + this.drawGrid(); + } + + if (this._background) { + this.updateBackgroundImage(this._background); + } + + return this; + }, + + // For storing the current transformation matrix (CTM) of the paper's viewport. + _viewportMatrix: null, + + // For verifying whether the CTM is up-to-date. The viewport transform attribute + // could have been manipulated directly. + _viewportTransformString: null, + + matrix: function(ctm) { + + var viewport = this.viewport; + + // Getter: + if (ctm === undefined) { + + var transformString = viewport.getAttribute('transform'); + + if ((this._viewportTransformString || null) === transformString) { + // It's ok to return the cached matrix. The transform attribute has not changed since + // the matrix was stored. + ctm = this._viewportMatrix; + } else { + // The viewport transform attribute has changed. Measure the matrix and cache again. + ctm = viewport.getCTM(); + this._viewportMatrix = ctm; + this._viewportTransformString = transformString; + } + + // Clone the cached current transformation matrix. + // If no matrix previously stored the identity matrix is returned. + return V.createSVGMatrix(ctm); + } + + // Setter: + ctm = V.createSVGMatrix(ctm); + ctmString = V.matrixToTransformString(ctm); + viewport.setAttribute('transform', ctmString); + this.tools.setAttribute('transform', ctmString); + + this._viewportMatrix = ctm; + this._viewportTransformString = viewport.getAttribute('transform'); + + return this; + }, + + clientMatrix: function() { + + return V.createSVGMatrix(this.viewport.getScreenCTM()); + }, + + _sortDelayingBatches: ['add', 'to-front', 'to-back'], + + _onSort: function() { + if (!this.model.hasActiveBatch(this._sortDelayingBatches)) { + this.sortViews(); + } + }, + + _onBatchStop: function(data) { + var name = data && data.batchName; + if (this._sortDelayingBatches.includes(name) && + !this.model.hasActiveBatch(this._sortDelayingBatches)) { + this.sortViews(); + } + }, + + onRemove: function() { + + //clean up all DOM elements/views to prevent memory leaks + this.removeViews(); + }, + + setDimensions: function(width, height) { + + width = this.options.width = width || this.options.width; + height = this.options.height = height || this.options.height; + + this.$el.css({ + width: Math.round(width), + height: Math.round(height) + }); + + this.trigger('resize', width, height); + }, + + setOrigin: function(ox, oy) { + + return this.translate(ox || 0, oy || 0, { absolute: true }); + }, + + // Expand/shrink the paper to fit the content. Snap the width/height to the grid + // defined in `gridWidth`, `gridHeight`. `padding` adds to the resulting width/height of the paper. + // When options { fitNegative: true } it also translates the viewport in order to make all + // the content visible. + fitToContent: function(gridWidth, gridHeight, padding, opt) { // alternatively function(opt) + + if (joint.util.isObject(gridWidth)) { + // first parameter is an option object + opt = gridWidth; + gridWidth = opt.gridWidth || 1; + gridHeight = opt.gridHeight || 1; + padding = opt.padding || 0; + + } else { + + opt = opt || {}; + gridWidth = gridWidth || 1; + gridHeight = gridHeight || 1; + padding = padding || 0; + } + + padding = joint.util.normalizeSides(padding); + + // Calculate the paper size to accomodate all the graph's elements. + var bbox = V(this.viewport).getBBox(); + + var currentScale = this.scale(); + var currentTranslate = this.translate(); + + bbox.x *= currentScale.sx; + bbox.y *= currentScale.sy; + bbox.width *= currentScale.sx; + bbox.height *= currentScale.sy; + + var calcWidth = Math.max(Math.ceil((bbox.width + bbox.x) / gridWidth), 1) * gridWidth; + var calcHeight = Math.max(Math.ceil((bbox.height + bbox.y) / gridHeight), 1) * gridHeight; + + var tx = 0; + var ty = 0; + + if ((opt.allowNewOrigin == 'negative' && bbox.x < 0) || (opt.allowNewOrigin == 'positive' && bbox.x >= 0) || opt.allowNewOrigin == 'any') { + tx = Math.ceil(-bbox.x / gridWidth) * gridWidth; + tx += padding.left; + calcWidth += tx; + } + + if ((opt.allowNewOrigin == 'negative' && bbox.y < 0) || (opt.allowNewOrigin == 'positive' && bbox.y >= 0) || opt.allowNewOrigin == 'any') { + ty = Math.ceil(-bbox.y / gridHeight) * gridHeight; + ty += padding.top; + calcHeight += ty; + } + + calcWidth += padding.right; + calcHeight += padding.bottom; + + // Make sure the resulting width and height are greater than minimum. + calcWidth = Math.max(calcWidth, opt.minWidth || 0); + calcHeight = Math.max(calcHeight, opt.minHeight || 0); + + // Make sure the resulting width and height are lesser than maximum. + calcWidth = Math.min(calcWidth, opt.maxWidth || Number.MAX_VALUE); + calcHeight = Math.min(calcHeight, opt.maxHeight || Number.MAX_VALUE); + + var dimensionChange = calcWidth != this.options.width || calcHeight != this.options.height; + var originChange = tx != currentTranslate.tx || ty != currentTranslate.ty; + + // Change the dimensions only if there is a size discrepency or an origin change + if (originChange) { + this.translate(tx, ty); + } + if (dimensionChange) { + this.setDimensions(calcWidth, calcHeight); + } + }, + + scaleContentToFit: function(opt) { + + var contentBBox = this.getContentBBox(); + + if (!contentBBox.width || !contentBBox.height) return; + + opt = opt || {}; + + joint.util.defaults(opt, { + padding: 0, + preserveAspectRatio: true, + scaleGrid: null, + minScale: 0, + maxScale: Number.MAX_VALUE + //minScaleX + //minScaleY + //maxScaleX + //maxScaleY + //fittingBBox + }); + + var padding = opt.padding; + + var minScaleX = opt.minScaleX || opt.minScale; + var maxScaleX = opt.maxScaleX || opt.maxScale; + var minScaleY = opt.minScaleY || opt.minScale; + var maxScaleY = opt.maxScaleY || opt.maxScale; + + var fittingBBox; + if (opt.fittingBBox) { + fittingBBox = opt.fittingBBox; + } else { + var currentTranslate = this.translate(); + fittingBBox = { + x: currentTranslate.tx, + y: currentTranslate.ty, + width: this.options.width, + height: this.options.height + }; + } + + fittingBBox = g.rect(fittingBBox).moveAndExpand({ + x: padding, + y: padding, + width: -2 * padding, + height: -2 * padding + }); + + var currentScale = this.scale(); + + var newSx = fittingBBox.width / contentBBox.width * currentScale.sx; + var newSy = fittingBBox.height / contentBBox.height * currentScale.sy; + + if (opt.preserveAspectRatio) { + newSx = newSy = Math.min(newSx, newSy); + } + + // snap scale to a grid + if (opt.scaleGrid) { + + var gridSize = opt.scaleGrid; + + newSx = gridSize * Math.floor(newSx / gridSize); + newSy = gridSize * Math.floor(newSy / gridSize); + } + + // scale min/max boundaries + newSx = Math.min(maxScaleX, Math.max(minScaleX, newSx)); + newSy = Math.min(maxScaleY, Math.max(minScaleY, newSy)); + + this.scale(newSx, newSy); + + var contentTranslation = this.getContentBBox(); + + var newOx = fittingBBox.x - contentTranslation.x; + var newOy = fittingBBox.y - contentTranslation.y; + + this.translate(newOx, newOy); + }, + + // Return the dimensions of the content area in local units (without transformations). + getContentArea: function() { + + return V(this.viewport).getBBox(); + }, + + // Return the dimensions of the content bbox in client units (as it appears on screen). + getContentBBox: function() { + + var crect = this.viewport.getBoundingClientRect(); + + // Using Screen CTM was the only way to get the real viewport bounding box working in both + // Google Chrome and Firefox. + var clientCTM = this.clientMatrix(); + + // for non-default origin we need to take the viewport translation into account + var currentTranslate = this.translate(); + + return g.rect({ + x: crect.left - clientCTM.e + currentTranslate.tx, + y: crect.top - clientCTM.f + currentTranslate.ty, + width: crect.width, + height: crect.height + }); + }, + + // Returns a geometry rectangle represeting the entire + // paper area (coordinates from the left paper border to the right one + // and the top border to the bottom one). + getArea: function() { + + return this.paperToLocalRect({ + x: 0, + y: 0, + width: this.options.width, + height: this.options.height + }); + }, + + getRestrictedArea: function() { + + var restrictedArea; + + if (joint.util.isFunction(this.options.restrictTranslate)) { + // A method returning a bounding box + restrictedArea = this.options.restrictTranslate.apply(this, arguments); + } else if (this.options.restrictTranslate === true) { + // The paper area + restrictedArea = this.getArea(); + } else { + // Either false or a bounding box + restrictedArea = this.options.restrictTranslate || null; + } + + return restrictedArea; + }, + + createViewForModel: function(cell) { + + // A class taken from the paper options. + var optionalViewClass; + + // A default basic class (either dia.ElementView or dia.LinkView) + var defaultViewClass; + + // A special class defined for this model in the corresponding namespace. + // e.g. joint.shapes.basic.Rect searches for joint.shapes.basic.RectView + var namespace = this.options.cellViewNamespace; + var type = cell.get('type') + 'View'; + var namespaceViewClass = joint.util.getByPath(namespace, type, '.'); + + if (cell.isLink()) { + optionalViewClass = this.options.linkView; + defaultViewClass = joint.dia.LinkView; + } else { + optionalViewClass = this.options.elementView; + defaultViewClass = joint.dia.ElementView; + } + + // a) the paper options view is a class (deprecated) + // 1. search the namespace for a view + // 2. if no view was found, use view from the paper options + // b) the paper options view is a function + // 1. call the function from the paper options + // 2. if no view was return, search the namespace for a view + // 3. if no view was found, use the default + var ViewClass = (optionalViewClass.prototype instanceof Backbone.View) + ? namespaceViewClass || optionalViewClass + : optionalViewClass.call(this, cell) || namespaceViewClass || defaultViewClass; + + return new ViewClass({ + model: cell, + interactive: this.options.interactive + }); + }, + + onCellAdded: function(cell, graph, opt) { + + if (this.options.async && opt.async !== false && joint.util.isNumber(opt.position)) { + + this._asyncCells = this._asyncCells || []; + this._asyncCells.push(cell); + + if (opt.position == 0) { + + if (this._frameId) throw new Error('another asynchronous rendering in progress'); + + this.asyncRenderViews(this._asyncCells, opt); + delete this._asyncCells; + } + + } else { + + this.renderView(cell); + } + }, + + removeView: function(cell) { + + var view = this._views[cell.id]; + + if (view) { + view.remove(); + delete this._views[cell.id]; + } + + return view; + }, + + renderView: function(cell) { + + var view = this._views[cell.id] = this.createViewForModel(cell); + + V(this.viewport).append(view.el); + view.paper = this; + view.render(); + + return view; + }, + + onImageDragStart: function() { + // This is the only way to prevent image dragging in Firefox that works. + // Setting -moz-user-select: none, draggable="false" attribute or user-drag: none didn't help. + + return false; + }, + + beforeRenderViews: function(cells) { + + // Make sure links are always added AFTER elements. + // They wouldn't find their sources/targets in the DOM otherwise. + cells.sort(function(a) { return (a.isLink()) ? 1 : -1; }); + + return cells; + }, + + afterRenderViews: function() { + + this.sortViews(); + }, + + resetViews: function(cellsCollection, opt) { + + // clearing views removes any event listeners + this.removeViews(); + + var cells = cellsCollection.models.slice(); + + // `beforeRenderViews()` can return changed cells array (e.g sorted). + cells = this.beforeRenderViews(cells, opt) || cells; + + this.cancelRenderViews(); + + if (this.options.async) { + + this.asyncRenderViews(cells, opt); + // Sort the cells once all elements rendered (see asyncRenderViews()). + + } else { + + for (var i = 0, n = cells.length; i < n; i++) { + this.renderView(cells[i]); + } + + // Sort the cells in the DOM manually as we might have changed the order they + // were added to the DOM (see above). + this.sortViews(); + } + }, + + cancelRenderViews: function() { + if (this._frameId) { + joint.util.cancelFrame(this._frameId); + delete this._frameId; + } + }, + + removeViews: function() { + + joint.util.invoke(this._views, 'remove'); + + this._views = {}; + }, + + asyncBatchAdded: joint.util.noop, + + asyncRenderViews: function(cells, opt) { + + if (this._frameId) { + + var batchSize = (this.options.async && this.options.async.batchSize) || 50; + var batchCells = cells.splice(0, batchSize); + + batchCells.forEach(function(cell) { + + // The cell has to be part of the graph. + // There is a chance in asynchronous rendering + // that a cell was removed before it's rendered to the paper. + if (cell.graph === this.model) this.renderView(cell); + + }, this); + + this.asyncBatchAdded(); + } + + if (!cells.length) { + + // No cells left to render. + delete this._frameId; + this.afterRenderViews(opt); + this.trigger('render:done', opt); + + } else { + + // Schedule a next batch to render. + this._frameId = joint.util.nextFrame(function() { + this.asyncRenderViews(cells, opt); + }, this); + } + }, + + sortViews: function() { + + // Run insertion sort algorithm in order to efficiently sort DOM elements according to their + // associated model `z` attribute. + + var $cells = $(this.viewport).children('[model-id]'); + var cells = this.model.get('cells'); + + joint.util.sortElements($cells, function(a, b) { + + var cellA = cells.get($(a).attr('model-id')); + var cellB = cells.get($(b).attr('model-id')); + + return (cellA.get('z') || 0) > (cellB.get('z') || 0) ? 1 : -1; + }); + }, + + scale: function(sx, sy, ox, oy) { + + // getter + if (sx === undefined) { + return V.matrixToScale(this.matrix()); + } + + // setter + if (sy === undefined) { + sy = sx; + } + if (ox === undefined) { + ox = 0; + oy = 0; + } + + var translate = this.translate(); + + if (ox || oy || translate.tx || translate.ty) { + var newTx = translate.tx - ox * (sx - 1); + var newTy = translate.ty - oy * (sy - 1); + this.translate(newTx, newTy); + } + + var ctm = this.matrix(); + ctm.a = sx || 0; + ctm.d = sy || 0; + + this.matrix(ctm); + + this.trigger('scale', sx, sy, ox, oy); + + return this; + }, + + // Experimental - do not use in production. + rotate: function(angle, cx, cy) { + + // getter + if (angle === undefined) { + return V.matrixToRotate(this.matrix()); + } + + // setter + + // If the origin is not set explicitely, rotate around the center. Note that + // we must use the plain bounding box (`this.el.getBBox()` instead of the one that gives us + // the real bounding box (`bbox()`) including transformations). + if (cx === undefined) { + var bbox = this.viewport.getBBox(); + cx = bbox.width / 2; + cy = bbox.height / 2; + } + + var ctm = this.matrix().translate(cx,cy).rotate(angle).translate(-cx,-cy); + this.matrix(ctm); + + return this; + }, + + translate: function(tx, ty) { + + // getter + if (tx === undefined) { + return V.matrixToTranslate(this.matrix()); + } + + // setter + + var ctm = this.matrix(); + ctm.e = tx || 0; + ctm.f = ty || 0; + + this.matrix(ctm); + + var newTranslate = this.translate(); + var origin = this.options.origin; + origin.x = newTranslate.tx; + origin.y = newTranslate.ty; + + this.trigger('translate', newTranslate.tx, newTranslate.ty); + + if (this.options.drawGrid) { + this.drawGrid(); + } + + return this; + }, + + // Find the first view climbing up the DOM tree starting at element `el`. Note that `el` can also + // be a selector or a jQuery object. + findView: function($el) { + + var el = joint.util.isString($el) + ? this.viewport.querySelector($el) + : $el instanceof $ ? $el[0] : $el; + + while (el && el !== this.el && el !== document) { + + var id = el.getAttribute('model-id'); + if (id) return this._views[id]; + + el = el.parentNode; + } + + return undefined; + }, + + // Find a view for a model `cell`. `cell` can also be a string or number representing a model `id`. + findViewByModel: function(cell) { + + var id = (joint.util.isString(cell) || joint.util.isNumber(cell)) ? cell : (cell && cell.id); + + return this._views[id]; + }, + + // Find all views at given point + findViewsFromPoint: function(p) { + + p = g.point(p); + + var views = this.model.getElements().map(this.findViewByModel, this); + + return views.filter(function(view) { + return view && view.vel.getBBox({ target: this.viewport }).containsPoint(p); + }, this); + }, + + // Find all views in given area + findViewsInArea: function(rect, opt) { + + opt = joint.util.defaults(opt || {}, { strict: false }); + rect = g.rect(rect); + + var views = this.model.getElements().map(this.findViewByModel, this); + var method = opt.strict ? 'containsRect' : 'intersect'; + + return views.filter(function(view) { + return view && rect[method](view.vel.getBBox({ target: this.viewport })); + }, this); + }, + + removeTools: function() { + joint.dia.CellView.dispatchToolsEvent(this, 'remove'); + return this; + }, + + hideTools: function() { + joint.dia.CellView.dispatchToolsEvent(this, 'hide'); + return this; + }, + + showTools: function() { + joint.dia.CellView.dispatchToolsEvent(this, 'show'); + return this; + }, + + getModelById: function(id) { + + return this.model.getCell(id); + }, + + snapToGrid: function(x, y) { + + // Convert global coordinates to the local ones of the `viewport`. Otherwise, + // improper transformation would be applied when the viewport gets transformed (scaled/rotated). + return this.clientToLocalPoint(x, y).snapToGrid(this.options.gridSize); + }, + + localToPaperPoint: function(x, y) { + // allow `x` to be a point and `y` undefined + var localPoint = g.Point(x, y); + var paperPoint = V.transformPoint(localPoint, this.matrix()); + return g.Point(paperPoint); + }, + + localToPaperRect: function(x, y, width, height) { + // allow `x` to be a rectangle and rest arguments undefined + var localRect = g.Rect(x, y); + var paperRect = V.transformRect(localRect, this.matrix()); + return g.Rect(paperRect); + }, + + paperToLocalPoint: function(x, y) { + // allow `x` to be a point and `y` undefined + var paperPoint = g.Point(x, y); + var localPoint = V.transformPoint(paperPoint, this.matrix().inverse()); + return g.Point(localPoint); + }, + + paperToLocalRect: function(x, y, width, height) { + // allow `x` to be a rectangle and rest arguments undefined + var paperRect = g.Rect(x, y, width, height); + var localRect = V.transformRect(paperRect, this.matrix().inverse()); + return g.Rect(localRect); + }, + + localToClientPoint: function(x, y) { + // allow `x` to be a point and `y` undefined + var localPoint = g.Point(x, y); + var clientPoint = V.transformPoint(localPoint, this.clientMatrix()); + return g.Point(clientPoint); + }, + + localToClientRect: function(x, y, width, height) { + // allow `x` to be a point and `y` undefined + var localRect = g.Rect(x, y, width, height); + var clientRect = V.transformRect(localRect, this.clientMatrix()); + return g.Rect(clientRect); + }, + + // Transform client coordinates to the paper local coordinates. + // Useful when you have a mouse event object and you'd like to get coordinates + // inside the paper that correspond to `evt.clientX` and `evt.clientY` point. + // Example: var localPoint = paper.clientToLocalPoint({ x: evt.clientX, y: evt.clientY }); + clientToLocalPoint: function(x, y) { + // allow `x` to be a point and `y` undefined + var clientPoint = g.Point(x, y); + var localPoint = V.transformPoint(clientPoint, this.clientMatrix().inverse()); + return g.Point(localPoint); + }, + + clientToLocalRect: function(x, y, width, height) { + // allow `x` to be a point and `y` undefined + var clientRect = g.Rect(x, y, width, height); + var localRect = V.transformRect(clientRect, this.clientMatrix().inverse()); + return g.Rect(localRect); + }, + + localToPagePoint: function(x, y) { + + return this.localToPaperPoint(x, y).offset(this.pageOffset()); + }, + + localToPageRect: function(x, y, width, height) { + + return this.localToPaperRect(x, y, width, height).moveAndExpand(this.pageOffset()); + }, + + pageToLocalPoint: function(x, y) { + + var pagePoint = g.Point(x, y); + var paperPoint = pagePoint.difference(this.pageOffset()); + return this.paperToLocalPoint(paperPoint); + }, + + pageToLocalRect: function(x, y, width, height) { + + var pageOffset = this.pageOffset(); + var paperRect = g.Rect(x, y, width, height); + paperRect.x -= pageOffset.x; + paperRect.y -= pageOffset.y; + return this.paperToLocalRect(paperRect); + }, + + clientOffset: function() { + + var clientRect = this.svg.getBoundingClientRect(); + return g.Point(clientRect.left, clientRect.top); + }, + + pageOffset: function() { + + return this.clientOffset().offset(window.scrollX, window.scrollY); + }, + + linkAllowed: function(linkView) { + + if (!(linkView instanceof joint.dia.LinkView)) { + throw new Error('Must provide a linkView.'); + } + + var link = linkView.model; + var paperOptions = this.options; + var graph = this.model; + var ns = graph.constructor.validations; + + if (!paperOptions.multiLinks) { + if (!ns.multiLinks.call(this, graph, link)) return false; + } + + if (!paperOptions.linkPinning) { + // Link pinning is not allowed and the link is not connected to the target. + if (!ns.linkPinning.call(this, graph, link)) return false; + } + + if (typeof paperOptions.allowLink === 'function') { + if (!paperOptions.allowLink.call(this, linkView, this)) return false; + } + + return true; + }, + + getDefaultLink: function(cellView, magnet) { + + return joint.util.isFunction(this.options.defaultLink) + // default link is a function producing link model + ? this.options.defaultLink.call(this, cellView, magnet) + // default link is the Backbone model + : this.options.defaultLink.clone(); + }, + + // Cell highlighting. + // ------------------ + + resolveHighlighter: function(opt) { + + opt = opt || {}; + var highlighterDef = opt.highlighter; + var paperOpt = this.options; + + /* + Expecting opt.highlighter to have the following structure: + { + name: 'highlighter-name', + options: { + some: 'value' + } + } + */ + if (highlighterDef === undefined) { + + // check for built-in types + var type = ['embedding', 'connecting', 'magnetAvailability', 'elementAvailability'].find(function(type) { + return !!opt[type]; + }); + + highlighterDef = (type && paperOpt.highlighting[type]) || paperOpt.highlighting['default']; + } + + // Do nothing if opt.highlighter is falsey. + // This allows the case to not highlight cell(s) in certain cases. + // For example, if you want to NOT highlight when embedding elements. + if (!highlighterDef) return false; + + // Allow specifying a highlighter by name. + if (joint.util.isString(highlighterDef)) { + highlighterDef = { + name: highlighterDef + }; + } + + var name = highlighterDef.name; + var highlighter = paperOpt.highlighterNamespace[name]; + + // Highlighter validation + if (!highlighter) { + throw new Error('Unknown highlighter ("' + name + '")'); + } + if (typeof highlighter.highlight !== 'function') { + throw new Error('Highlighter ("' + name + '") is missing required highlight() method'); + } + if (typeof highlighter.unhighlight !== 'function') { + throw new Error('Highlighter ("' + name + '") is missing required unhighlight() method'); + } + + return { + highlighter: highlighter, + options: highlighterDef.options || {}, + name: name + }; + }, + + onCellHighlight: function(cellView, magnetEl, opt) { + + opt = this.resolveHighlighter(opt); + if (!opt) return; + if (!magnetEl.id) { + magnetEl.id = V.uniqueId(); + } + + var key = opt.name + magnetEl.id + JSON.stringify(opt.options); + if (!this._highlights[key]) { + + var highlighter = opt.highlighter; + highlighter.highlight(cellView, magnetEl, joint.util.assign({}, opt.options)); + + this._highlights[key] = { + cellView: cellView, + magnetEl: magnetEl, + opt: opt.options, + highlighter: highlighter + }; + } + }, + + onCellUnhighlight: function(cellView, magnetEl, opt) { + + opt = this.resolveHighlighter(opt); + if (!opt) return; + + var key = opt.name + magnetEl.id + JSON.stringify(opt.options); + var highlight = this._highlights[key]; + if (highlight) { + + // Use the cellView and magnetEl that were used by the highlighter.highlight() method. + highlight.highlighter.unhighlight(highlight.cellView, highlight.magnetEl, highlight.opt); + + this._highlights[key] = null; + } + }, + + // Interaction. + // ------------ + + pointerdblclick: function(evt) { + + evt.preventDefault(); + + evt = joint.util.normalizeEvent(evt); + + var view = this.findView(evt.target); + if (this.guard(evt, view)) return; + + var localPoint = this.snapToGrid({ x: evt.clientX, y: evt.clientY }); + + if (view) { + view.pointerdblclick(evt, localPoint.x, localPoint.y); + + } else { + this.trigger('blank:pointerdblclick', evt, localPoint.x, localPoint.y); + } + }, + + pointerclick: function(evt) { + + // Trigger event only if mouse has not moved. + if (this._mousemoved <= this.options.clickThreshold) { + + evt = joint.util.normalizeEvent(evt); + + var view = this.findView(evt.target); + if (this.guard(evt, view)) return; + + var localPoint = this.snapToGrid({ x: evt.clientX, y: evt.clientY }); + + if (view) { + view.pointerclick(evt, localPoint.x, localPoint.y); + + } else { + this.trigger('blank:pointerclick', evt, localPoint.x, localPoint.y); + } + } + }, + + contextmenu: function(evt) { + + if (this.options.preventContextMenu) evt.preventDefault(); + + evt = joint.util.normalizeEvent(evt); + + var view = this.findView(evt.target); + if (this.guard(evt, view)) return; + + var localPoint = this.snapToGrid({ x: evt.clientX, y: evt.clientY }); + + if (view) { + view.contextmenu(evt, localPoint.x, localPoint.y); + + } else { + this.trigger('blank:contextmenu', evt, localPoint.x, localPoint.y); + } + }, + + pointerdown: function(evt) { + + evt = joint.util.normalizeEvent(evt); + + var view = this.findView(evt.target); + if (this.guard(evt, view)) return; + + var localPoint = this.snapToGrid({ x: evt.clientX, y: evt.clientY }); + + if (view) { + + evt.preventDefault(); + view.pointerdown(evt, localPoint.x, localPoint.y); + + } else { + + if (this.options.preventDefaultBlankAction) evt.preventDefault(); + + this.trigger('blank:pointerdown', evt, localPoint.x, localPoint.y); + } + + this.delegateDragEvents(view, evt.data); + }, + + pointermove: function(evt) { + + evt.preventDefault(); + + // mouse moved counter + var data = this.eventData(evt); + data.mousemoved || (data.mousemoved = 0); + var mousemoved = ++data.mousemoved; + if (mousemoved <= this.options.moveThreshold) return; + + evt = joint.util.normalizeEvent(evt); + + var localPoint = this.snapToGrid({ x: evt.clientX, y: evt.clientY }); + + var view = data.sourceView; + if (view) { + view.pointermove(evt, localPoint.x, localPoint.y); + } else { + this.trigger('blank:pointermove', evt, localPoint.x, localPoint.y); + } + + this.eventData(evt, data); + }, + + pointerup: function(evt) { + + this.undelegateDocumentEvents(); + + evt = joint.util.normalizeEvent(evt); + + var localPoint = this.snapToGrid({ x: evt.clientX, y: evt.clientY }); + + var view = this.eventData(evt).sourceView; + if (view) { + view.pointerup(evt, localPoint.x, localPoint.y); + } else { + this.trigger('blank:pointerup', evt, localPoint.x, localPoint.y); + } + + this.delegateEvents(); + }, + + mouseover: function(evt) { + + evt = joint.util.normalizeEvent(evt); + + var view = this.findView(evt.target); + if (this.guard(evt, view)) return; + + if (view) { + view.mouseover(evt); + + } else { + if (this.el === evt.target) return; // prevent border of paper from triggering this + this.trigger('blank:mouseover', evt); + } + }, + + mouseout: function(evt) { + + evt = joint.util.normalizeEvent(evt); + + var view = this.findView(evt.target); + if (this.guard(evt, view)) return; + + if (view) { + view.mouseout(evt); + + } else { + if (this.el === evt.target) return; // prevent border of paper from triggering this + this.trigger('blank:mouseout', evt); + } + }, + + mouseenter: function(evt) { + + evt = joint.util.normalizeEvent(evt); + + var view = this.findView(evt.target); + if (this.guard(evt, view)) return; + var relatedView = this.findView(evt.relatedTarget); + if (view) { + // mouse moved from tool over view? + if (relatedView === view) return; + view.mouseenter(evt); + } else { + if (relatedView) return; + // `paper` (more descriptive), not `blank` + this.trigger('paper:mouseenter', evt); + } + }, + + mouseleave: function(evt) { + + evt = joint.util.normalizeEvent(evt); + + var view = this.findView(evt.target); + if (this.guard(evt, view)) return; + var relatedView = this.findView(evt.relatedTarget); + if (view) { + // mouse moved from view over tool? + if (relatedView === view) return; + view.mouseleave(evt); + } else { + if (relatedView) return; + // `paper` (more descriptive), not `blank` + this.trigger('paper:mouseleave', evt); + } + }, + + mousewheel: function(evt) { + + evt = joint.util.normalizeEvent(evt); + + var view = this.findView(evt.target); + if (this.guard(evt, view)) return; + + var originalEvent = evt.originalEvent; + var localPoint = this.snapToGrid({ x: originalEvent.clientX, y: originalEvent.clientY }); + var delta = Math.max(-1, Math.min(1, (originalEvent.wheelDelta || -originalEvent.detail))); + + if (view) { + view.mousewheel(evt, localPoint.x, localPoint.y, delta); + + } else { + this.trigger('blank:mousewheel', evt, localPoint.x, localPoint.y, delta); + } + }, + + onevent: function(evt) { + + var eventNode = evt.currentTarget; + var eventName = eventNode.getAttribute('event'); + if (eventName) { + var view = this.findView(eventNode); + if (view) { + + evt = joint.util.normalizeEvent(evt); + if (this.guard(evt, view)) return; + + var localPoint = this.snapToGrid({ x: evt.clientX, y: evt.clientY }); + view.onevent(evt, eventName, localPoint.x, localPoint.y); + } + } + }, + + onmagnet: function(evt) { + + var magnetNode = evt.currentTarget; + var magnetValue = magnetNode.getAttribute('magnet'); + if (magnetValue) { + var view = this.findView(magnetNode); + if (view) { + + evt = joint.util.normalizeEvent(evt); + if (this.guard(evt, view)) return; + if (!this.options.validateMagnet(view, magnetNode)) return; + + var localPoint = this.snapToGrid(evt.clientX, evt.clientY); + view.onmagnet(evt, localPoint.x, localPoint.y); + } + } + }, + + onlabel: function(evt) { + + var labelNode = evt.currentTarget; + var view = this.findView(labelNode); + if (view) { + + evt = joint.util.normalizeEvent(evt); + if (this.guard(evt, view)) return; + + var localPoint = this.snapToGrid(evt.clientX, evt.clientY); + view.onlabel(evt, localPoint.x, localPoint.y); + } + }, + + delegateDragEvents: function(view, data) { + + data || (data = {}); + this.eventData({ data: data }, { sourceView: view || null, mousemoved: 0 }); + this.delegateDocumentEvents(null, data); + this.undelegateEvents(); + }, + + // Guard the specified event. If the event is not interesting, guard returns `true`. + // Otherwise, it returns `false`. + guard: function(evt, view) { + + if (this.options.guard && this.options.guard(evt, view)) { + return true; + } + + if (evt.data && evt.data.guarded !== undefined) { + return evt.data.guarded; + } + + if (view && view.model && (view.model instanceof joint.dia.Cell)) { + return false; + } + + if (this.svg === evt.target || this.el === evt.target || $.contains(this.svg, evt.target)) { + return false; + } + + return true; // Event guarded. Paper should not react on it in any way. + }, + + setGridSize: function(gridSize) { + + this.options.gridSize = gridSize; + + if (this.options.drawGrid) { + this.drawGrid(); + } + + return this; + }, + + clearGrid: function() { + + if (this.$grid) { + this.$grid.css('backgroundImage', 'none'); + } + return this; + }, + + _getGriRefs: function() { + + if (!this._gridCache) { + + this._gridCache = { + root: V('svg', { width: '100%', height: '100%' }, V('defs')), + patterns: {}, + add: function(id, vel) { + V(this.root.node.childNodes[0]).append(vel); + this.patterns[id] = vel; + this.root.append(V('rect', { width: "100%", height: "100%", fill: 'url(#' + id + ')' })); + }, + get: function(id) { + return this.patterns[id] + }, + exist: function(id) { + return this.patterns[id] !== undefined; + } + } + } + + return this._gridCache; + }, + + setGrid: function(drawGrid) { + + this.clearGrid(); + + this._gridCache = null; + this._gridSettings = []; + + var optionsList = Array.isArray(drawGrid) ? drawGrid : [drawGrid || {}]; + optionsList.forEach(function(item) { + this._gridSettings.push.apply(this._gridSettings, this._resolveDrawGridOption(item)); + }, this); + return this; + }, + + _resolveDrawGridOption: function(opt) { + + var namespace = this.constructor.gridPatterns; + if (joint.util.isString(opt) && Array.isArray(namespace[opt])) { + return namespace[opt].map(function(item) { + return joint.util.assign({}, item); + }); + } + + var options = opt || { args: [{}] }; + var isArray = Array.isArray(options); + var name = options.name; + + if (!isArray && !name && !options.markup ) { + name = 'dot'; + } + + if (name && Array.isArray(namespace[name])) { + var pattern = namespace[name].map(function(item) { + return joint.util.assign({}, item); + }); + + var args = Array.isArray(options.args) ? options.args : [options.args || {}]; + + joint.util.defaults(args[0], joint.util.omit(opt, 'args')); + for (var i = 0; i < args.length; i++) { + if (pattern[i]) { + joint.util.assign(pattern[i], args[i]); + } + } + return pattern; + } + + return isArray ? options : [options]; + }, + + drawGrid: function(opt) { + + var gridSize = this.options.gridSize; + if (gridSize <= 1) { + return this.clearGrid(); + } + + var localOptions = Array.isArray(opt) ? opt : [opt]; + + var ctm = this.matrix(); + var refs = this._getGriRefs(); + + this._gridSettings.forEach(function(gridLayerSetting, index) { + + var id = 'pattern_' + index; + var options = joint.util.merge(gridLayerSetting, localOptions[index], { + sx: ctm.a || 1, + sy: ctm.d || 1, + ox: ctm.e || 0, + oy: ctm.f || 0 + }); + + options.width = gridSize * (ctm.a || 1) * (options.scaleFactor || 1); + options.height = gridSize * (ctm.d || 1) * (options.scaleFactor || 1); + + if (!refs.exist(id)) { + refs.add(id, V('pattern', { id: id, patternUnits: 'userSpaceOnUse' }, V(options.markup))); + } + + var patternDefVel = refs.get(id); + + if (joint.util.isFunction(options.update)) { + options.update(patternDefVel.node.childNodes[0], options); + } + + var x = options.ox % options.width; + if (x < 0) x += options.width; + + var y = options.oy % options.height; + if (y < 0) y += options.height; + + patternDefVel.attr({ + x: x, + y: y, + width: options.width, + height: options.height + }); + }); + + var patternUri = new XMLSerializer().serializeToString(refs.root.node); + patternUri = 'url(data:image/svg+xml;base64,' + btoa(patternUri) + ')'; + + this.$grid.css('backgroundImage', patternUri); + + return this; + }, + + updateBackgroundImage: function(opt) { + + opt = opt || {}; + + var backgroundPosition = opt.position || 'center'; + var backgroundSize = opt.size || 'auto auto'; + + var currentScale = this.scale(); + var currentTranslate = this.translate(); + + // backgroundPosition + if (joint.util.isObject(backgroundPosition)) { + var x = currentTranslate.tx + (currentScale.sx * (backgroundPosition.x || 0)); + var y = currentTranslate.ty + (currentScale.sy * (backgroundPosition.y || 0)); + backgroundPosition = x + 'px ' + y + 'px'; + } + + // backgroundSize + if (joint.util.isObject(backgroundSize)) { + backgroundSize = g.rect(backgroundSize).scale(currentScale.sx, currentScale.sy); + backgroundSize = backgroundSize.width + 'px ' + backgroundSize.height + 'px'; + } + + this.$background.css({ + backgroundSize: backgroundSize, + backgroundPosition: backgroundPosition + }); + }, + + drawBackgroundImage: function(img, opt) { + + // Clear the background image if no image provided + if (!(img instanceof HTMLImageElement)) { + this.$background.css('backgroundImage', ''); + return; + } + + opt = opt || {}; + + var backgroundImage; + var backgroundSize = opt.size; + var backgroundRepeat = opt.repeat || 'no-repeat'; + var backgroundOpacity = opt.opacity || 1; + var backgroundQuality = Math.abs(opt.quality) || 1; + var backgroundPattern = this.constructor.backgroundPatterns[joint.util.camelCase(backgroundRepeat)]; + + if (joint.util.isFunction(backgroundPattern)) { + // 'flip-x', 'flip-y', 'flip-xy', 'watermark' and custom + img.width *= backgroundQuality; + img.height *= backgroundQuality; + var canvas = backgroundPattern(img, opt); + if (!(canvas instanceof HTMLCanvasElement)) { + throw new Error('dia.Paper: background pattern must return an HTML Canvas instance'); + } + + backgroundImage = canvas.toDataURL('image/png'); + backgroundRepeat = 'repeat'; + if (joint.util.isObject(backgroundSize)) { + // recalculate the tile size if an object passed in + backgroundSize.width *= canvas.width / img.width; + backgroundSize.height *= canvas.height / img.height; + } else if (backgroundSize === undefined) { + // calcule the tile size if no provided + opt.size = { + width: canvas.width / backgroundQuality, + height: canvas.height / backgroundQuality + }; + } + } else { + // backgroundRepeat: + // no-repeat', 'round', 'space', 'repeat', 'repeat-x', 'repeat-y' + backgroundImage = img.src; + if (backgroundSize === undefined) { + // pass the image size for the backgroundSize if no size provided + opt.size = { + width: img.width, + height: img.height + }; + } + } + + this.$background.css({ + opacity: backgroundOpacity, + backgroundRepeat: backgroundRepeat, + backgroundImage: 'url(' + backgroundImage + ')' + }); + + this.updateBackgroundImage(opt); + }, + + updateBackgroundColor: function(color) { + + this.$el.css('backgroundColor', color || ''); + }, + + drawBackground: function(opt) { + + opt = opt || {}; + + this.updateBackgroundColor(opt.color); + + if (opt.image) { + opt = this._background = joint.util.cloneDeep(opt); + var img = document.createElement('img'); + img.onload = this.drawBackgroundImage.bind(this, img, opt); + img.src = opt.image; + } else { + this.drawBackgroundImage(null); + this._background = null; + } + + return this; + }, + + setInteractivity: function(value) { + + this.options.interactive = value; + + joint.util.invoke(this._views, 'setInteractivity', value); + }, + + // Paper definitions. + // ------------------ + + isDefined: function(defId) { + + return !!this.svg.getElementById(defId); + }, + + defineFilter: function(filter) { + + if (!joint.util.isObject(filter)) { + throw new TypeError('dia.Paper: defineFilter() requires 1. argument to be an object.'); + } + + var filterId = filter.id; + var name = filter.name; + // Generate a hash code from the stringified filter definition. This gives us + // a unique filter ID for different definitions. + if (!filterId) { + filterId = name + this.svg.id + joint.util.hashCode(JSON.stringify(filter)); + } + // If the filter already exists in the document, + // we're done and we can just use it (reference it using `url()`). + // If not, create one. + if (!this.isDefined(filterId)) { + + var namespace = joint.util.filter; + var filterSVGString = namespace[name] && namespace[name](filter.args || {}); + if (!filterSVGString) { + throw new Error('Non-existing filter ' + name); + } + + // Set the filter area to be 3x the bounding box of the cell + // and center the filter around the cell. + var filterAttrs = joint.util.assign({ + filterUnits: 'objectBoundingBox', + x: -1, + y: -1, + width: 3, + height: 3 + }, filter.attrs, { + id: filterId + }); + + V(filterSVGString, filterAttrs).appendTo(this.defs); + } + + return filterId; + }, + + defineGradient: function(gradient) { + + if (!joint.util.isObject(gradient)) { + throw new TypeError('dia.Paper: defineGradient() requires 1. argument to be an object.'); + } + + var gradientId = gradient.id; + var type = gradient.type; + var stops = gradient.stops; + // Generate a hash code from the stringified filter definition. This gives us + // a unique filter ID for different definitions. + if (!gradientId) { + gradientId = type + this.svg.id + joint.util.hashCode(JSON.stringify(gradient)); + } + // If the gradient already exists in the document, + // we're done and we can just use it (reference it using `url()`). + // If not, create one. + if (!this.isDefined(gradientId)) { + + var stopTemplate = joint.util.template(''); + var gradientStopsStrings = joint.util.toArray(stops).map(function(stop) { + return stopTemplate({ + offset: stop.offset, + color: stop.color, + opacity: Number.isFinite(stop.opacity) ? stop.opacity : 1 + }); + }); + + var gradientSVGString = [ + '<' + type + '>', + gradientStopsStrings.join(''), + '' + ].join(''); + + var gradientAttrs = joint.util.assign({ id: gradientId }, gradient.attrs); + + V(gradientSVGString, gradientAttrs).appendTo(this.defs); + } + + return gradientId; + }, + + defineMarker: function(marker) { + + if (!joint.util.isObject(marker)) { + throw new TypeError('dia.Paper: defineMarker() requires 1. argument to be an object.'); + } + + var markerId = marker.id; + + // Generate a hash code from the stringified filter definition. This gives us + // a unique filter ID for different definitions. + if (!markerId) { + markerId = this.svg.id + joint.util.hashCode(JSON.stringify(marker)); + } + + if (!this.isDefined(markerId)) { + + var attrs = joint.util.omit(marker, 'type', 'userSpaceOnUse'); + var pathMarker = V('marker', { + id: markerId, + orient: 'auto', + overflow: 'visible', + markerUnits: marker.markerUnits || 'userSpaceOnUse' + }, [ + V(marker.type || 'path', attrs) + ]); + + pathMarker.appendTo(this.defs); + } + + return markerId; + } +}, { + + backgroundPatterns: { + + flipXy: function(img) { + // d b + // q p + + var canvas = document.createElement('canvas'); + var imgWidth = img.width; + var imgHeight = img.height; + + canvas.width = 2 * imgWidth; + canvas.height = 2 * imgHeight; + + var ctx = canvas.getContext('2d'); + // top-left image + ctx.drawImage(img, 0, 0, imgWidth, imgHeight); + // xy-flipped bottom-right image + ctx.setTransform(-1, 0, 0, -1, canvas.width, canvas.height); + ctx.drawImage(img, 0, 0, imgWidth, imgHeight); + // x-flipped top-right image + ctx.setTransform(-1, 0, 0, 1, canvas.width, 0); + ctx.drawImage(img, 0, 0, imgWidth, imgHeight); + // y-flipped bottom-left image + ctx.setTransform(1, 0, 0, -1, 0, canvas.height); + ctx.drawImage(img, 0, 0, imgWidth, imgHeight); + + return canvas; + }, + + flipX: function(img) { + // d b + // d b + + var canvas = document.createElement('canvas'); + var imgWidth = img.width; + var imgHeight = img.height; + + canvas.width = imgWidth * 2; + canvas.height = imgHeight; + + var ctx = canvas.getContext('2d'); + // left image + ctx.drawImage(img, 0, 0, imgWidth, imgHeight); + // flipped right image + ctx.translate(2 * imgWidth, 0); + ctx.scale(-1, 1); + ctx.drawImage(img, 0, 0, imgWidth, imgHeight); + + return canvas; + }, + + flipY: function(img) { + // d d + // q q + + var canvas = document.createElement('canvas'); + var imgWidth = img.width; + var imgHeight = img.height; + + canvas.width = imgWidth; + canvas.height = imgHeight * 2; + + var ctx = canvas.getContext('2d'); + // top image + ctx.drawImage(img, 0, 0, imgWidth, imgHeight); + // flipped bottom image + ctx.translate(0, 2 * imgHeight); + ctx.scale(1, -1); + ctx.drawImage(img, 0, 0, imgWidth, imgHeight); + + return canvas; + }, + + watermark: function(img, opt) { + // d + // d + + opt = opt || {}; + + var imgWidth = img.width; + var imgHeight = img.height; + + var canvas = document.createElement('canvas'); + canvas.width = imgWidth * 3; + canvas.height = imgHeight * 3; + + var ctx = canvas.getContext('2d'); + var angle = joint.util.isNumber(opt.watermarkAngle) ? -opt.watermarkAngle : -20; + var radians = g.toRad(angle); + var stepX = canvas.width / 4; + var stepY = canvas.height / 4; + + for (var i = 0; i < 4; i ++) { + for (var j = 0; j < 4; j++) { + if ((i + j) % 2 > 0) { + // reset the current transformations + ctx.setTransform(1, 0, 0, 1, (2 * i - 1) * stepX, (2 * j - 1) * stepY); + ctx.rotate(radians); + ctx.drawImage(img, -imgWidth / 2, -imgHeight / 2, imgWidth, imgHeight); + } + } + } + + return canvas; + } + }, + + gridPatterns: { + dot: [{ + color: '#AAAAAA', + thickness: 1, + markup: 'rect', + update: function(el, opt) { + V(el).attr({ + width: opt.thickness * opt.sx, + height: opt.thickness * opt.sy, + fill: opt.color + }); + } + }], + fixedDot: [{ + color: '#AAAAAA', + thickness: 1, + markup: 'rect', + update: function(el, opt) { + var size = opt.sx <= 1 ? opt.thickness * opt.sx : opt.thickness; + V(el).attr({ width: size, height: size, fill: opt.color }); + } + }], + mesh: [{ + color: '#AAAAAA', + thickness: 1, + markup: 'path', + update: function(el, opt) { + + var d; + var width = opt.width; + var height = opt.height; + var thickness = opt.thickness; + + if (width - thickness >= 0 && height - thickness >= 0) { + d = ['M', width, 0, 'H0 M0 0 V0', height].join(' '); + } else { + d = 'M 0 0 0 0'; + } + + V(el).attr({ 'd': d, stroke: opt.color, 'stroke-width': opt.thickness }); + } + }], + doubleMesh: [{ + color: '#AAAAAA', + thickness: 1, + markup: 'path', + update: function(el, opt) { + + var d; + var width = opt.width; + var height = opt.height; + var thickness = opt.thickness; + + if (width - thickness >= 0 && height - thickness >= 0) { + d = ['M', width, 0, 'H0 M0 0 V0', height].join(' '); + } else { + d = 'M 0 0 0 0'; + } + + V(el).attr({ 'd': d, stroke: opt.color, 'stroke-width': opt.thickness }); + } + }, { + color: '#000000', + thickness: 3, + scaleFactor: 4, + markup: 'path', + update: function(el, opt) { + + var d; + var width = opt.width; + var height = opt.height; + var thickness = opt.thickness; + + if (width - thickness >= 0 && height - thickness >= 0) { + d = ['M', width, 0, 'H0 M0 0 V0', height].join(' '); + } else { + d = 'M 0 0 0 0'; + } + + V(el).attr({ 'd': d, stroke: opt.color, 'stroke-width': opt.thickness }); + } + }] + } +}); + +(function(joint, _, util) { + + var PortData = function(data) { + + var clonedData = util.cloneDeep(data) || {}; + this.ports = []; + this.groups = {}; + this.portLayoutNamespace = joint.layout.Port; + this.portLabelLayoutNamespace = joint.layout.PortLabel; + + this._init(clonedData); + }; + + PortData.prototype = { + + getPorts: function() { + return this.ports; + }, + + getGroup: function(name) { + return this.groups[name] || {}; + }, + + getPortsByGroup: function(groupName) { + + return this.ports.filter(function(port) { + return port.group === groupName; + }); + }, + + getGroupPortsMetrics: function(groupName, elBBox) { + + var group = this.getGroup(groupName); + var ports = this.getPortsByGroup(groupName); + + var groupPosition = group.position || {}; + var groupPositionName = groupPosition.name; + var namespace = this.portLayoutNamespace; + if (!namespace[groupPositionName]) { + groupPositionName = 'left'; + } + + var groupArgs = groupPosition.args || {}; + var portsArgs = ports.map(function(port) { + return port && port.position && port.position.args; + }); + var groupPortTransformations = namespace[groupPositionName](portsArgs, elBBox, groupArgs); + + var accumulator = { + ports: ports, + result: [] + }; + + util.toArray(groupPortTransformations).reduce(function(res, portTransformation, index) { + var port = res.ports[index]; + res.result.push({ + portId: port.id, + portTransformation: portTransformation, + labelTransformation: this._getPortLabelLayout(port, g.Point(portTransformation), elBBox), + portAttrs: port.attrs, + portSize: port.size, + labelSize: port.label.size + }); + return res; + }.bind(this), accumulator); + + return accumulator.result; + }, + + _getPortLabelLayout: function(port, portPosition, elBBox) { + + var namespace = this.portLabelLayoutNamespace; + var labelPosition = port.label.position.name || 'left'; + + if (namespace[labelPosition]) { + return namespace[labelPosition](portPosition, elBBox, port.label.position.args); + } + + return null; + }, + + _init: function(data) { + + // prepare groups + if (util.isObject(data.groups)) { + var groups = Object.keys(data.groups); + for (var i = 0, n = groups.length; i < n; i++) { + var key = groups[i]; + this.groups[key] = this._evaluateGroup(data.groups[key]); + } + } + + // prepare ports + var ports = util.toArray(data.items); + for (var j = 0, m = ports.length; j < m; j++) { + this.ports.push(this._evaluatePort(ports[j])); + } + }, + + _evaluateGroup: function(group) { + + return util.merge(group, { + position: this._getPosition(group.position, true), + label: this._getLabel(group, true) + }); + }, + + _evaluatePort: function(port) { + + var evaluated = util.assign({}, port); + + var group = this.getGroup(port.group); + + evaluated.markup = evaluated.markup || group.markup; + evaluated.attrs = util.merge({}, group.attrs, evaluated.attrs); + evaluated.position = this._createPositionNode(group, evaluated); + evaluated.label = util.merge({}, group.label, this._getLabel(evaluated)); + evaluated.z = this._getZIndex(group, evaluated); + evaluated.size = util.assign({}, group.size, evaluated.size); + + return evaluated; + }, + + _getZIndex: function(group, port) { + + if (util.isNumber(port.z)) { + return port.z; + } + if (util.isNumber(group.z) || group.z === 'auto') { + return group.z; + } + return 'auto'; + }, + + _createPositionNode: function(group, port) { + + return util.merge({ + name: 'left', + args: {} + }, group.position, { args: port.args }); + }, + + _getPosition: function(position, setDefault) { + + var args = {}; + var positionName; + + if (util.isFunction(position)) { + positionName = 'fn'; + args.fn = position; + } else if (util.isString(position)) { + positionName = position; + } else if (position === undefined) { + positionName = setDefault ? 'left' : null; + } else if (Array.isArray(position)) { + positionName = 'absolute'; + args.x = position[0]; + args.y = position[1]; + } else if (util.isObject(position)) { + positionName = position.name; + util.assign(args, position.args); + } + + var result = { args: args }; + + if (positionName) { + result.name = positionName; + } + return result; + }, + + _getLabel: function(item, setDefaults) { + + var label = item.label || {}; + + var ret = label; + ret.position = this._getPosition(label.position, setDefaults); + + return ret; + } + }; + + util.assign(joint.dia.Element.prototype, { + + _initializePorts: function() { + + this._createPortData(); + this.on('change:ports', function() { + + this._processRemovedPort(); + this._createPortData(); + }, this); + }, + + /** + * remove links tied wiht just removed element + * @private + */ + _processRemovedPort: function() { + + var current = this.get('ports') || {}; + var currentItemsMap = {}; + + util.toArray(current.items).forEach(function(item) { + currentItemsMap[item.id] = true; + }); + + var previous = this.previous('ports') || {}; + var removed = {}; + + util.toArray(previous.items).forEach(function(item) { + if (!currentItemsMap[item.id]) { + removed[item.id] = true; + } + }); + + var graph = this.graph; + if (graph && !util.isEmpty(removed)) { + + var inboundLinks = graph.getConnectedLinks(this, { inbound: true }); + inboundLinks.forEach(function(link) { + + if (removed[link.get('target').port]) link.remove(); + }); + + var outboundLinks = graph.getConnectedLinks(this, { outbound: true }); + outboundLinks.forEach(function(link) { + + if (removed[link.get('source').port]) link.remove(); + }); + } + }, + + /** + * @returns {boolean} + */ + hasPorts: function() { + + return this.prop('ports/items').length > 0; + }, + + /** + * @param {string} id + * @returns {boolean} + */ + hasPort: function(id) { + + return this.getPortIndex(id) !== -1; + }, + + /** + * @returns {Array} + */ + getPorts: function() { + + return util.cloneDeep(this.prop('ports/items')) || []; + }, + + /** + * @param {string} id + * @returns {object} + */ + getPort: function(id) { + + return util.cloneDeep(util.toArray(this.prop('ports/items')).find( function(port) { + return port.id && port.id === id; + })); + }, + + /** + * @param {string} groupName + * @returns {Object} + */ + getPortsPositions: function(groupName) { + + var portsMetrics = this._portSettingsData.getGroupPortsMetrics(groupName, g.Rect(this.size())); + + return portsMetrics.reduce(function(positions, metrics) { + var transformation = metrics.portTransformation; + positions[metrics.portId] = { + x: transformation.x, + y: transformation.y, + angle: transformation.angle + }; + return positions; + }, {}); + }, + + /** + * @param {string|Port} port port id or port + * @returns {number} port index + */ + getPortIndex: function(port) { + + var id = util.isObject(port) ? port.id : port; + + if (!this._isValidPortId(id)) { + return -1; + } + + return util.toArray(this.prop('ports/items')).findIndex(function(item) { + return item.id === id; + }); + }, + + /** + * @param {object} port + * @param {object} [opt] + * @returns {joint.dia.Element} + */ + addPort: function(port, opt) { + + if (!util.isObject(port) || Array.isArray(port)) { + throw new Error('Element: addPort requires an object.'); + } + + var ports = util.assign([], this.prop('ports/items')); + ports.push(port); + this.prop('ports/items', ports, opt); + + return this; + }, + + /** + * @param {string} portId + * @param {string|object=} path + * @param {*=} value + * @param {object=} opt + * @returns {joint.dia.Element} + */ + portProp: function(portId, path, value, opt) { + + var index = this.getPortIndex(portId); + + if (index === -1) { + throw new Error('Element: unable to find port with id ' + portId); + } + + var args = Array.prototype.slice.call(arguments, 1); + if (Array.isArray(path)) { + args[0] = ['ports', 'items', index].concat(path); + } else if (util.isString(path)) { + + // Get/set an attribute by a special path syntax that delimits + // nested objects by the colon character. + args[0] = ['ports/items/', index, '/', path].join(''); + + } else { + + args = ['ports/items/' + index]; + if (util.isPlainObject(path)) { + args.push(path); + args.push(value); + } + } + + return this.prop.apply(this, args); + }, + + _validatePorts: function() { + + var portsAttr = this.get('ports') || {}; + + var errorMessages = []; + portsAttr = portsAttr || {}; + var ports = util.toArray(portsAttr.items); + + ports.forEach(function(p) { + + if (typeof p !== 'object') { + errorMessages.push('Element: invalid port ', p); + } + + if (!this._isValidPortId(p.id)) { + p.id = util.uuid(); + } + }, this); + + if (joint.util.uniq(ports, 'id').length !== ports.length) { + errorMessages.push('Element: found id duplicities in ports.'); + } + + return errorMessages; + }, + + /** + * @param {string} id port id + * @returns {boolean} + * @private + */ + _isValidPortId: function(id) { + + return id !== null && id !== undefined && !util.isObject(id); + }, + + addPorts: function(ports, opt) { + + if (ports.length) { + this.prop('ports/items', util.assign([], this.prop('ports/items')).concat(ports), opt); + } + + return this; + }, + + removePort: function(port, opt) { + + var options = opt || {}; + var ports = util.assign([], this.prop('ports/items')); + + var index = this.getPortIndex(port); + + if (index !== -1) { + ports.splice(index, 1); + options.rewrite = true; + this.prop('ports/items', ports, options); + } + + return this; + }, + + /** + * @private + */ + _createPortData: function() { + + var err = this._validatePorts(); + + if (err.length > 0) { + this.set('ports', this.previous('ports')); + throw new Error(err.join(' ')); + } + + var prevPortData; + + if (this._portSettingsData) { + + prevPortData = this._portSettingsData.getPorts(); + } + + this._portSettingsData = new PortData(this.get('ports')); + + var curPortData = this._portSettingsData.getPorts(); + + if (prevPortData) { + + var added = curPortData.filter(function(item) { + if (!prevPortData.find(function(prevPort) { return prevPort.id === item.id;})) { + return item; + } + }); + + var removed = prevPortData.filter(function(item) { + if (!curPortData.find(function(curPort) { return curPort.id === item.id;})) { + return item; + } + }); + + if (removed.length > 0) { + this.trigger('ports:remove', this, removed); + } + + if (added.length > 0) { + this.trigger('ports:add', this, added); + } + } + } + }); + + util.assign(joint.dia.ElementView.prototype, { + + portContainerMarkup: 'g', + portMarkup: [{ + tagName: 'circle', + selector: 'circle', + attributes: { + 'r': 10, + 'fill': '#FFFFFF', + 'stroke': '#000000' + } + }], + portLabelMarkup: [{ + tagName: 'text', + selector: 'text', + attributes: { + 'fill': '#000000' + } + }], + /** @type {Object} */ + _portElementsCache: null, + + /** + * @private + */ + _initializePorts: function() { + + this._portElementsCache = {}; + + this.listenTo(this.model, 'change:ports', function() { + + this._refreshPorts(); + }); + }, + + /** + * @typedef {Object} Port + * + * @property {string} id + * @property {Object} position + * @property {Object} label + * @property {Object} attrs + * @property {string} markup + * @property {string} group + */ + + /** + * @private + */ + _refreshPorts: function() { + + this._removePorts(); + this._portElementsCache = {}; + + this._renderPorts(); + }, + + /** + * @private + */ + _renderPorts: function() { + + // references to rendered elements without z-index + var elementReferences = []; + var elem = this._getContainerElement(); + + for (var i = 0, count = elem.node.childNodes.length; i < count; i++) { + elementReferences.push(elem.node.childNodes[i]); + } + + var portsGropsByZ = util.groupBy(this.model._portSettingsData.getPorts(), 'z'); + var withoutZKey = 'auto'; + + // render non-z first + util.toArray(portsGropsByZ[withoutZKey]).forEach(function(port) { + var portElement = this._getPortElement(port); + elem.append(portElement); + elementReferences.push(portElement); + }, this); + + var groupNames = Object.keys(portsGropsByZ); + for (var k = 0; k < groupNames.length; k++) { + var groupName = groupNames[k]; + if (groupName !== withoutZKey) { + var z = parseInt(groupName, 10); + this._appendPorts(portsGropsByZ[groupName], z, elementReferences); + } + } + + this._updatePorts(); + }, + + /** + * @returns {V} + * @private + */ + _getContainerElement: function() { + + return this.rotatableNode || this.vel; + }, + + /** + * @param {Array}ports + * @param {number} z + * @param refs + * @private + */ + _appendPorts: function(ports, z, refs) { + + var containerElement = this._getContainerElement(); + var portElements = util.toArray(ports).map(this._getPortElement, this); + + if (refs[z] || z < 0) { + V(refs[Math.max(z, 0)]).before(portElements); + } else { + containerElement.append(portElements); + } + }, + + /** + * Try to get element from cache, + * @param port + * @returns {*} + * @private + */ + _getPortElement: function(port) { + + if (this._portElementsCache[port.id]) { + return this._portElementsCache[port.id].portElement; + } + return this._createPortElement(port); + }, + + findPortNode: function(portId, selector) { + var portCache = this._portElementsCache[portId]; + if (!portCache) return null; + var portRoot = portCache.portContentElement.node; + var portSelectors = portCache.portContentSelectors; + return this.findBySelector(selector, portRoot, portSelectors)[0]; + }, + + /** + * @private + */ + _updatePorts: function() { + + // layout ports without group + this._updatePortGroup(undefined); + // layout ports with explicit group + var groupsNames = Object.keys(this.model._portSettingsData.groups); + groupsNames.forEach(this._updatePortGroup, this); + }, + + /** + * @private + */ + _removePorts: function() { + util.invoke(this._portElementsCache, 'portElement.remove'); + }, + + /** + * @param {Port} port + * @returns {V} + * @private + */ + _createPortElement: function(port) { + + + var portElement; + var labelElement; + + var portMarkup = this._getPortMarkup(port); + var portSelectors; + if (Array.isArray(portMarkup)) { + var portDoc = util.parseDOMJSON(portMarkup); + var portFragment = portDoc.fragment; + if (portFragment.childNodes.length > 1) { + portElement = V('g').append(portFragment); + } else { + portElement = V(portFragment.firstChild); + } + portSelectors = portDoc.selectors; + } else { + portElement = V(portMarkup); + if (Array.isArray(portElement)) { + portElement = V('g').append(portElement); + } + } + + if (!portElement) { + throw new Error('ElementView: Invalid port markup.'); + } + + portElement.attr({ + 'port': port.id, + 'port-group': port.group + }); + + var labelMarkup = this._getPortLabelMarkup(port.label); + var labelSelectors; + if (Array.isArray(labelMarkup)) { + var labelDoc = util.parseDOMJSON(labelMarkup); + var labelFragment = labelDoc.fragment; + if (labelFragment.childNodes.length > 1) { + labelElement = V('g').append(labelFragment); + } else { + labelElement = V(labelFragment.firstChild); + } + labelSelectors = labelDoc.selectors; + } else { + labelElement = V(labelMarkup); + if (Array.isArray(labelElement)) { + labelElement = V('g').append(labelElement); + } + } + + if (!labelElement) { + throw new Error('ElementView: Invalid port label markup.'); + } + + var portContainerSelectors; + if (portSelectors && labelSelectors) { + for (var key in labelSelectors) { + if (portSelectors[key]) throw new Error('ElementView: selectors within port must be unique.'); + } + portContainerSelectors = util.assign({}, portSelectors, labelSelectors); + } else { + portContainerSelectors = portSelectors || labelSelectors; + } + + var portContainerElement = V(this.portContainerMarkup) + .addClass('joint-port') + .append([ + portElement.addClass('joint-port-body'), + labelElement.addClass('joint-port-label') + ]); + + this._portElementsCache[port.id] = { + portElement: portContainerElement, + portLabelElement: labelElement, + portSelectors: portContainerSelectors, + portLabelSelectors: labelSelectors, + portContentElement: portElement, + portContentSelectors: portSelectors + }; + + return portContainerElement; + }, + + /** + * @param {string=} groupName + * @private + */ + _updatePortGroup: function(groupName) { + + var elementBBox = g.Rect(this.model.size()); + var portsMetrics = this.model._portSettingsData.getGroupPortsMetrics(groupName, elementBBox); + + for (var i = 0, n = portsMetrics.length; i < n; i++) { + var metrics = portsMetrics[i]; + var portId = metrics.portId; + var cached = this._portElementsCache[portId] || {}; + var portTransformation = metrics.portTransformation; + this.applyPortTransform(cached.portElement, portTransformation); + this.updateDOMSubtreeAttributes(cached.portElement.node, metrics.portAttrs, { + rootBBox: new g.Rect(metrics.portSize), + selectors: cached.portSelectors + }); + + var labelTransformation = metrics.labelTransformation; + if (labelTransformation) { + this.applyPortTransform(cached.portLabelElement, labelTransformation, (-portTransformation.angle || 0)); + this.updateDOMSubtreeAttributes(cached.portLabelElement.node, labelTransformation.attrs, { + rootBBox: new g.Rect(metrics.labelSize), + selectors: cached.portLabelSelectors + }); + } + } + }, + + /** + * @param {Vectorizer} element + * @param {{dx:number, dy:number, angle: number, attrs: Object, x:number: y:number}} transformData + * @param {number=} initialAngle + * @constructor + */ + applyPortTransform: function(element, transformData, initialAngle) { + + var matrix = V.createSVGMatrix() + .rotate(initialAngle || 0) + .translate(transformData.x || 0, transformData.y || 0) + .rotate(transformData.angle || 0); + + element.transform(matrix, { absolute: true }); + }, + + /** + * @param {Port} port + * @returns {string} + * @private + */ + _getPortMarkup: function(port) { + + return port.markup || this.model.get('portMarkup') || this.model.portMarkup || this.portMarkup; + }, + + /** + * @param {Object} label + * @returns {string} + * @private + */ + _getPortLabelMarkup: function(label) { + + return label.markup || this.model.get('portLabelMarkup') || this.model.portLabelMarkup || this.portLabelMarkup; + } + + }); +}(joint, _, joint.util)); + +joint.dia.Element.define('basic.Generic', { + attrs: { + '.': { fill: '#ffffff', stroke: 'none' } + } +}); + +joint.shapes.basic.Generic.define('basic.Rect', { + attrs: { + 'rect': { + fill: '#ffffff', + stroke: '#000000', + width: 100, + height: 60 + }, + 'text': { + fill: '#000000', + text: '', + 'font-size': 14, + 'ref-x': .5, + 'ref-y': .5, + 'text-anchor': 'middle', + 'y-alignment': 'middle', + 'font-family': 'Arial, helvetica, sans-serif' + } + } +}, { + markup: '' +}); + +joint.shapes.basic.TextView = joint.dia.ElementView.extend({ + + initialize: function() { + joint.dia.ElementView.prototype.initialize.apply(this, arguments); + // The element view is not automatically rescaled to fit the model size + // when the attribute 'attrs' is changed. + this.listenTo(this.model, 'change:attrs', this.resize); + } +}); + +joint.shapes.basic.Generic.define('basic.Text', { + attrs: { + 'text': { + 'font-size': 18, + fill: '#000000' + } + } +}, { + markup: '', +}); + +joint.shapes.basic.Generic.define('basic.Circle', { + size: { width: 60, height: 60 }, + attrs: { + 'circle': { + fill: '#ffffff', + stroke: '#000000', + r: 30, + cx: 30, + cy: 30 + }, + 'text': { + 'font-size': 14, + text: '', + 'text-anchor': 'middle', + 'ref-x': .5, + 'ref-y': .5, + 'y-alignment': 'middle', + fill: '#000000', + 'font-family': 'Arial, helvetica, sans-serif' + } + } +}, { + markup: '', +}); + +joint.shapes.basic.Generic.define('basic.Ellipse', { + size: { width: 60, height: 40 }, + attrs: { + 'ellipse': { + fill: '#ffffff', + stroke: '#000000', + rx: 30, + ry: 20, + cx: 30, + cy: 20 + }, + 'text': { + 'font-size': 14, + text: '', + 'text-anchor': 'middle', + 'ref-x': .5, + 'ref-y': .5, + 'y-alignment': 'middle', + fill: '#000000', + 'font-family': 'Arial, helvetica, sans-serif' + } + } +}, { + markup: '', +}); + +joint.shapes.basic.Generic.define('basic.Polygon', { + size: { width: 60, height: 40 }, + attrs: { + 'polygon': { + fill: '#ffffff', + stroke: '#000000' + }, + 'text': { + 'font-size': 14, + text: '', + 'text-anchor': 'middle', + 'ref-x': .5, + 'ref-dy': 20, + 'y-alignment': 'middle', + fill: '#000000', + 'font-family': 'Arial, helvetica, sans-serif' + } + } +}, { + markup: '', +}); + +joint.shapes.basic.Generic.define('basic.Polyline', { + size: { width: 60, height: 40 }, + attrs: { + 'polyline': { + fill: '#ffffff', + stroke: '#000000' + }, + 'text': { + 'font-size': 14, + text: '', + 'text-anchor': 'middle', + 'ref-x': .5, + 'ref-dy': 20, + 'y-alignment': 'middle', + fill: '#000000', + 'font-family': 'Arial, helvetica, sans-serif' + } + } +}, { + markup: '', +}); + +joint.shapes.basic.Generic.define('basic.Image', { + attrs: { + 'text': { + 'font-size': 14, + text: '', + 'text-anchor': 'middle', + 'ref-x': .5, + 'ref-dy': 20, + 'y-alignment': 'middle', + fill: '#000000', + 'font-family': 'Arial, helvetica, sans-serif' + } + } +}, { + markup: '', +}); + +joint.shapes.basic.Generic.define('basic.Path', { + size: { width: 60, height: 60 }, + attrs: { + 'path': { + fill: '#ffffff', + stroke: '#000000' + }, + 'text': { + 'font-size': 14, + text: '', + 'text-anchor': 'middle', + 'ref': 'path', + 'ref-x': .5, + 'ref-dy': 10, + fill: '#000000', + 'font-family': 'Arial, helvetica, sans-serif' + } + } + +}, { + markup: '', +}); + +joint.shapes.basic.Path.define('basic.Rhombus', { + attrs: { + 'path': { + d: 'M 30 0 L 60 30 30 60 0 30 z' + }, + 'text': { + 'ref-y': .5, + 'ref-dy': null, + 'y-alignment': 'middle' + } + } +}); + + +/** + * @deprecated use the port api instead + */ +// PortsModelInterface is a common interface for shapes that have ports. This interface makes it easy +// to create new shapes with ports functionality. It is assumed that the new shapes have +// `inPorts` and `outPorts` array properties. Only these properties should be used to set ports. +// In other words, using this interface, it is no longer recommended to set ports directly through the +// `attrs` object. + +// Usage: +// joint.shapes.custom.MyElementWithPorts = joint.shapes.basic.Path.extend(_.extend({}, joint.shapes.basic.PortsModelInterface, { +// getPortAttrs: function(portName, index, total, selector, type) { +// var attrs = {}; +// var portClass = 'port' + index; +// var portSelector = selector + '>.' + portClass; +// var portTextSelector = portSelector + '>text'; +// var portBodySelector = portSelector + '>.port-body'; +// +// attrs[portTextSelector] = { text: portName }; +// attrs[portBodySelector] = { port: { id: portName || _.uniqueId(type) , type: type } }; +// attrs[portSelector] = { ref: 'rect', 'ref-y': (index + 0.5) * (1 / total) }; +// +// if (selector === '.outPorts') { attrs[portSelector]['ref-dx'] = 0; } +// +// return attrs; +// } +//})); +joint.shapes.basic.PortsModelInterface = { + + initialize: function() { + + this.updatePortsAttrs(); + this.on('change:inPorts change:outPorts', this.updatePortsAttrs, this); + + // Call the `initialize()` of the parent. + this.constructor.__super__.constructor.__super__.initialize.apply(this, arguments); + }, + + updatePortsAttrs: function(eventName) { + + if (this._portSelectors) { + + var newAttrs = joint.util.omit(this.get('attrs'), this._portSelectors); + this.set('attrs', newAttrs, { silent: true }); + } + + // This holds keys to the `attrs` object for all the port specific attribute that + // we set in this method. This is necessary in order to remove previously set + // attributes for previous ports. + this._portSelectors = []; + + var attrs = {}; + + joint.util.toArray(this.get('inPorts')).forEach(function(portName, index, ports) { + var portAttributes = this.getPortAttrs(portName, index, ports.length, '.inPorts', 'in'); + this._portSelectors = this._portSelectors.concat(Object.keys(portAttributes)); + joint.util.assign(attrs, portAttributes); + }, this); + + joint.util.toArray(this.get('outPorts')).forEach(function(portName, index, ports) { + var portAttributes = this.getPortAttrs(portName, index, ports.length, '.outPorts', 'out'); + this._portSelectors = this._portSelectors.concat(Object.keys(portAttributes)); + joint.util.assign(attrs, portAttributes); + }, this); + + // Silently set `attrs` on the cell so that noone knows the attrs have changed. This makes sure + // that, for example, command manager does not register `change:attrs` command but only + // the important `change:inPorts`/`change:outPorts` command. + this.attr(attrs, { silent: true }); + // Manually call the `processPorts()` method that is normally called on `change:attrs` (that we just made silent). + this.processPorts(); + // Let the outside world (mainly the `ModelView`) know that we're done configuring the `attrs` object. + this.trigger('process:ports'); + }, + + getPortSelector: function(name) { + + var selector = '.inPorts'; + var index = this.get('inPorts').indexOf(name); + + if (index < 0) { + selector = '.outPorts'; + index = this.get('outPorts').indexOf(name); + + if (index < 0) throw new Error("getPortSelector(): Port doesn't exist."); + } + + return selector + '>g:nth-child(' + (index + 1) + ')>.port-body'; + } +}; + +joint.shapes.basic.PortsViewInterface = { + + initialize: function() { + + // `Model` emits the `process:ports` whenever it's done configuring the `attrs` object for ports. + this.listenTo(this.model, 'process:ports', this.update); + + joint.dia.ElementView.prototype.initialize.apply(this, arguments); + }, + + update: function() { + + // First render ports so that `attrs` can be applied to those newly created DOM elements + // in `ElementView.prototype.update()`. + this.renderPorts(); + joint.dia.ElementView.prototype.update.apply(this, arguments); + }, + + renderPorts: function() { + + var $inPorts = this.$('.inPorts').empty(); + var $outPorts = this.$('.outPorts').empty(); + + var portTemplate = joint.util.template(this.model.portMarkup); + + var ports = this.model.ports || []; + ports.filter(function(p) { + return p.type === 'in'; + }).forEach(function(port, index) { + + $inPorts.append(V(portTemplate({ id: index, port: port })).node); + }); + + ports.filter(function(p) { + return p.type === 'out'; + }).forEach(function(port, index) { + + $outPorts.append(V(portTemplate({ id: index, port: port })).node); + }); + } +}; + +joint.shapes.basic.Generic.define('basic.TextBlock', { + // see joint.css for more element styles + attrs: { + rect: { + fill: '#ffffff', + stroke: '#000000', + width: 80, + height: 100 + }, + text: { + fill: '#000000', + 'font-size': 14, + 'font-family': 'Arial, helvetica, sans-serif' + }, + '.content': { + text: '', + 'ref-x': .5, + 'ref-y': .5, + 'y-alignment': 'middle', + 'x-alignment': 'middle' + } + }, + + content: '' +}, { + markup: [ + '', + '', + joint.env.test('svgforeignobject') ? '
' : '', + '' + ].join(''), + + initialize: function() { + + this.listenTo(this, 'change:size', this.updateSize); + this.listenTo(this, 'change:content', this.updateContent); + this.updateSize(this, this.get('size')); + this.updateContent(this, this.get('content')); + joint.shapes.basic.Generic.prototype.initialize.apply(this, arguments); + }, + + updateSize: function(cell, size) { + + // Selector `foreignObject' doesn't work across all browsers, we're using class selector instead. + // We have to clone size as we don't want attributes.div.style to be same object as attributes.size. + this.attr({ + '.fobj': joint.util.assign({}, size), + div: { + style: joint.util.assign({}, size) + } + }); + }, + + updateContent: function(cell, content) { + + if (joint.env.test('svgforeignobject')) { + + // Content element is a
element. + this.attr({ + '.content': { + html: joint.util.sanitizeHTML(content) + } + }); + + } else { + + // Content element is a element. + // SVG elements don't have innerHTML attribute. + this.attr({ + '.content': { + text: content + } + }); + } + }, + + // Here for backwards compatibility: + setForeignObjectSize: function() { + + this.updateSize.apply(this, arguments); + }, + + // Here for backwards compatibility: + setDivContent: function() { + + this.updateContent.apply(this, arguments); + } +}); + +// TextBlockView implements the fallback for IE when no foreignObject exists and +// the text needs to be manually broken. +joint.shapes.basic.TextBlockView = joint.dia.ElementView.extend({ + + initialize: function() { + + joint.dia.ElementView.prototype.initialize.apply(this, arguments); + + // Keep this for backwards compatibility: + this.noSVGForeignObjectElement = !joint.env.test('svgforeignobject'); + + if (!joint.env.test('svgforeignobject')) { + + this.listenTo(this.model, 'change:content change:size', function(cell) { + // avoiding pass of extra paramters + this.updateContent(cell); + }); + } + }, + + update: function(cell, renderingOnlyAttrs) { + + var model = this.model; + + if (!joint.env.test('svgforeignobject')) { + + // Update everything but the content first. + var noTextAttrs = joint.util.omit(renderingOnlyAttrs || model.get('attrs'), '.content'); + joint.dia.ElementView.prototype.update.call(this, model, noTextAttrs); + + if (!renderingOnlyAttrs || joint.util.has(renderingOnlyAttrs, '.content')) { + // Update the content itself. + this.updateContent(model, renderingOnlyAttrs); + } + + } else { + + joint.dia.ElementView.prototype.update.call(this, model, renderingOnlyAttrs); + } + }, + + updateContent: function(cell, renderingOnlyAttrs) { + + // Create copy of the text attributes + var textAttrs = joint.util.merge({}, (renderingOnlyAttrs || cell.get('attrs'))['.content']); + + textAttrs = joint.util.omit(textAttrs, 'text'); + + // Break the content to fit the element size taking into account the attributes + // set on the model. + var text = joint.util.breakText(cell.get('content'), cell.get('size'), textAttrs, { + // measuring sandbox svg document + svgDocument: this.paper.svg + }); + + // Create a new attrs with same structure as the model attrs { text: { *textAttributes* }} + var attrs = joint.util.setByPath({}, '.content', textAttrs, '/'); + + // Replace text attribute with the one we just processed. + attrs['.content'].text = text; + + // Update the view using renderingOnlyAttributes parameter. + joint.dia.ElementView.prototype.update.call(this, cell, attrs); + } +}); + +(function(dia, util, env, V) { + + 'use strict'; + + // ELEMENTS + + var Element = dia.Element; + + Element.define('standard.Rectangle', { + attrs: { + body: { + refWidth: '100%', + refHeight: '100%', + strokeWidth: 2, + stroke: '#000000', + fill: '#FFFFFF' + }, + label: { + textVerticalAnchor: 'middle', + textAnchor: 'middle', + refX: '50%', + refY: '50%', + fontSize: 14, + fill: '#333333' + } + } + }, { + markup: [{ + tagName: 'rect', + selector: 'body', + }, { + tagName: 'text', + selector: 'label' + }] + }); + + Element.define('standard.Circle', { + attrs: { + body: { + refCx: '50%', + refCy: '50%', + refR: '50%', + strokeWidth: 2, + stroke: '#333333', + fill: '#FFFFFF' + }, + label: { + textVerticalAnchor: 'middle', + textAnchor: 'middle', + refX: '50%', + refY: '50%', + fontSize: 14, + fill: '#333333' + } + } + }, { + markup: [{ + tagName: 'circle', + selector: 'body' + }, { + tagName: 'text', + selector: 'label' + }] + }); + + Element.define('standard.Ellipse', { + attrs: { + body: { + refCx: '50%', + refCy: '50%', + refRx: '50%', + refRy: '50%', + strokeWidth: 2, + stroke: '#333333', + fill: '#FFFFFF' + }, + label: { + textVerticalAnchor: 'middle', + textAnchor: 'middle', + refX: '50%', + refY: '50%', + fontSize: 14, + fill: '#333333' + } + } + }, { + markup: [{ + tagName: 'ellipse', + selector: 'body' + }, { + tagName: 'text', + selector: 'label' + }] + }); + + Element.define('standard.Path', { + attrs: { + body: { + refD: 'M 0 0 L 10 0 10 10 0 10 Z', + strokeWidth: 2, + stroke: '#333333', + fill: '#FFFFFF' + }, + label: { + textVerticalAnchor: 'middle', + textAnchor: 'middle', + refX: '50%', + refY: '50%', + fontSize: 14, + fill: '#333333' + } + } + }, { + markup: [{ + tagName: 'path', + selector: 'body' + }, { + tagName: 'text', + selector: 'label' + }] + }); + + Element.define('standard.Polygon', { + attrs: { + body: { + refPoints: '0 0 10 0 10 10 0 10', + strokeWidth: 2, + stroke: '#333333', + fill: '#FFFFFF' + }, + label: { + textVerticalAnchor: 'middle', + textAnchor: 'middle', + refX: '50%', + refY: '50%', + fontSize: 14, + fill: '#333333' + } + } + }, { + markup: [{ + tagName: 'polygon', + selector: 'body' + }, { + tagName: 'text', + selector: 'label' + }] + }); + + Element.define('standard.Polyline', { + attrs: { + body: { + refPoints: '0 0 10 0 10 10 0 10 0 0', + strokeWidth: 2, + stroke: '#333333', + fill: '#FFFFFF' + }, + label: { + textVerticalAnchor: 'middle', + textAnchor: 'middle', + refX: '50%', + refY: '50%', + fontSize: 14, + fill: '#333333' + } + } + }, { + markup: [{ + tagName: 'polyline', + selector: 'body' + }, { + tagName: 'text', + selector: 'label' + }] + }); + + Element.define('standard.Image', { + attrs: { + image: { + refWidth: '100%', + refHeight: '100%', + // xlinkHref: '[URL]' + }, + label: { + textVerticalAnchor: 'top', + textAnchor: 'middle', + refX: '50%', + refY: '100%', + refY2: 10, + fontSize: 14, + fill: '#333333' + } + } + }, { + markup: [{ + tagName: 'image', + selector: 'image' + }, { + tagName: 'text', + selector: 'label' + }] + }); + + Element.define('standard.BorderedImage', { + attrs: { + border: { + refWidth: '100%', + refHeight: '100%', + stroke: '#333333', + strokeWidth: 2 + }, + image: { + // xlinkHref: '[URL]' + refWidth: -1, + refHeight: -1, + x: 0.5, + y: 0.5 + }, + label: { + textVerticalAnchor: 'top', + textAnchor: 'middle', + refX: '50%', + refY: '100%', + refY2: 10, + fontSize: 14, + fill: '#333333' + } + } + }, { + markup: [{ + tagName: 'image', + selector: 'image' + }, { + tagName: 'rect', + selector: 'border', + attributes: { + 'fill': 'none' + } + }, { + tagName: 'text', + selector: 'label' + }] + }); + + Element.define('standard.EmbeddedImage', { + attrs: { + body: { + refWidth: '100%', + refHeight: '100%', + stroke: '#333333', + fill: '#FFFFFF', + strokeWidth: 2 + }, + image: { + // xlinkHref: '[URL]' + refWidth: '30%', + refHeight: -20, + x: 10, + y: 10, + preserveAspectRatio: 'xMidYMin' + }, + label: { + textVerticalAnchor: 'top', + textAnchor: 'left', + refX: '30%', + refX2: 20, // 10 + 10 + refY: 10, + fontSize: 14, + fill: '#333333' + } + } + }, { + markup: [{ + tagName: 'rect', + selector: 'body' + }, { + tagName: 'image', + selector: 'image' + }, { + tagName: 'text', + selector: 'label' + }] + }); + + Element.define('standard.HeaderedRectangle', { + attrs: { + body: { + refWidth: '100%', + refHeight: '100%', + strokeWidth: 2, + stroke: '#000000', + fill: '#FFFFFF' + }, + header: { + refWidth: '100%', + height: 30, + strokeWidth: 2, + stroke: '#000000', + fill: '#FFFFFF' + }, + headerText: { + textVerticalAnchor: 'middle', + textAnchor: 'middle', + refX: '50%', + refY: 15, + fontSize: 16, + fill: '#333333' + }, + bodyText: { + textVerticalAnchor: 'middle', + textAnchor: 'middle', + refX: '50%', + refY: '50%', + refY2: 15, + fontSize: 14, + fill: '#333333' + } + } + }, { + markup: [{ + tagName: 'rect', + selector: 'body' + }, { + tagName: 'rect', + selector: 'header' + }, { + tagName: 'text', + selector: 'headerText' + }, { + tagName: 'text', + selector: 'bodyText' + }] + }); + + var CYLINDER_TILT = 10; + + joint.dia.Element.define('standard.Cylinder', { + attrs: { + body: { + lateralArea: CYLINDER_TILT, + fill: '#FFFFFF', + stroke: '#333333', + strokeWidth: 2 + }, + top: { + refCx: '50%', + cy: CYLINDER_TILT, + refRx: '50%', + ry: CYLINDER_TILT, + fill: '#FFFFFF', + stroke: '#333333', + strokeWidth: 2 + }, + label: { + textVerticalAnchor: 'middle', + textAnchor: 'middle', + refX: '50%', + refY: '100%', + refY2: 15, + fontSize: 14, + fill: '#333333' + } + } + }, { + markup: [{ + tagName: 'path', + selector: 'body' + }, { + tagName: 'ellipse', + selector: 'top' + }, { + tagName: 'text', + selector: 'label' + }], + + topRy: function(t, opt) { + // getter + if (t === undefined) return this.attr('body/lateralArea'); + + // setter + var isPercentage = util.isPercentage(t); + + var bodyAttrs = { lateralArea: t }; + var topAttrs = isPercentage + ? { refCy: t, refRy: t, cy: null, ry: null } + : { refCy: null, refRy: null, cy: t, ry: t }; + + return this.attr({ body: bodyAttrs, top: topAttrs }, opt); + } + + }, { + attributes: { + lateralArea: { + set: function(t, refBBox) { + var isPercentage = util.isPercentage(t); + if (isPercentage) t = parseFloat(t) / 100; + + var x = refBBox.x; + var y = refBBox.y; + var w = refBBox.width; + var h = refBBox.height; + + // curve control point variables + var rx = w / 2; + var ry = isPercentage ? (h * t) : t; + + var kappa = V.KAPPA; + var cx = kappa * rx; + var cy = kappa * (isPercentage ? (h * t) : t); + + // shape variables + var xLeft = x; + var xCenter = x + (w / 2); + var xRight = x + w; + + var ySideTop = y + ry; + var yCurveTop = ySideTop - ry; + var ySideBottom = y + h - ry; + var yCurveBottom = y + h; + + // return calculated shape + var data = [ + 'M', xLeft, ySideTop, + 'L', xLeft, ySideBottom, + 'C', x, (ySideBottom + cy), (xCenter - cx), yCurveBottom, xCenter, yCurveBottom, + 'C', (xCenter + cx), yCurveBottom, xRight, (ySideBottom + cy), xRight, ySideBottom, + 'L', xRight, ySideTop, + 'C', xRight, (ySideTop - cy), (xCenter + cx), yCurveTop, xCenter, yCurveTop, + 'C', (xCenter - cx), yCurveTop, xLeft, (ySideTop - cy), xLeft, ySideTop, + 'Z' + ]; + return { d: data.join(' ') }; + } + } + } + }); + + var foLabelMarkup = { + tagName: 'foreignObject', + selector: 'foreignObject', + attributes: { + 'overflow': 'hidden' + }, + children: [{ + tagName: 'div', + namespaceURI: 'http://www.w3.org/1999/xhtml', + selector: 'label', + style: { + width: '100%', + height: '100%', + position: 'static', + backgroundColor: 'transparent', + textAlign: 'center', + margin: 0, + padding: '0px 5px', + boxSizing: 'border-box', + display: 'flex', + alignItems: 'center', + justifyContent: 'center' + } + }] + }; + + var svgLabelMarkup = { + tagName: 'text', + selector: 'label', + attributes: { + 'text-anchor': 'middle' + } + }; + + Element.define('standard.TextBlock', { + attrs: { + body: { + refWidth: '100%', + refHeight: '100%', + stroke: '#333333', + fill: '#ffffff', + strokeWidth: 2 + }, + foreignObject: { + refWidth: '100%', + refHeight: '100%' + }, + label: { + style: { + fontSize: 14 + } + } + } + }, { + markup: [{ + tagName: 'rect', + selector: 'body' + }, + (env.test('svgforeignobject')) ? foLabelMarkup : svgLabelMarkup + ] + }, { + attributes: { + text: { + set: function(text, refBBox, node, attrs) { + if (node instanceof HTMLElement) { + node.textContent = text; + } else { + // No foreign object + var style = attrs.style || {}; + var wrapValue = { text: text, width: -5, height: '100%' }; + var wrapAttrs = util.assign({ textVerticalAnchor: 'middle' }, style); + dia.attributes.textWrap.set.call(this, wrapValue, refBBox, node, wrapAttrs); + return { fill: style.color || null }; + } + }, + position: function(text, refBBox, node) { + // No foreign object + if (node instanceof SVGElement) return refBBox.center(); + } + } + } + }); + + // LINKS + + var Link = dia.Link; + + Link.define('standard.Link', { + attrs: { + line: { + connection: true, + stroke: '#333333', + strokeWidth: 2, + strokeLinejoin: 'round', + targetMarker: { + type: 'path', + d: 'M 10 -5 0 0 10 5 z' + } + }, + wrapper: { + connection: true, + strokeWidth: 10, + strokeLinejoin: 'round' + } + } + }, { + markup: [{ + tagName: 'path', + selector: 'wrapper', + attributes: { + 'fill': 'none', + 'cursor': 'pointer', + 'stroke': 'transparent' + } + }, { + tagName: 'path', + selector: 'line', + attributes: { + 'fill': 'none', + 'pointer-events': 'none' + } + }] + }); + + Link.define('standard.DoubleLink', { + attrs: { + line: { + connection: true, + stroke: '#DDDDDD', + strokeWidth: 4, + strokeLinejoin: 'round', + targetMarker: { + type: 'path', + stroke: '#000000', + d: 'M 10 -3 10 -10 -2 0 10 10 10 3' + } + }, + outline: { + connection: true, + stroke: '#000000', + strokeWidth: 6, + strokeLinejoin: 'round' + } + } + }, { + markup: [{ + tagName: 'path', + selector: 'outline', + attributes: { + 'fill': 'none' + } + }, { + tagName: 'path', + selector: 'line', + attributes: { + 'fill': 'none' + } + }] + }); + + Link.define('standard.ShadowLink', { + attrs: { + line: { + connection: true, + stroke: '#FF0000', + strokeWidth: 20, + strokeLinejoin: 'round', + targetMarker: { + 'type': 'path', + 'stroke': 'none', + 'd': 'M 0 -10 -10 0 0 10 z' + }, + sourceMarker: { + 'type': 'path', + 'stroke': 'none', + 'd': 'M -10 -10 0 0 -10 10 0 10 0 -10 z' + } + }, + shadow: { + connection: true, + refX: 3, + refY: 6, + stroke: '#000000', + strokeOpacity: 0.2, + strokeWidth: 20, + strokeLinejoin: 'round', + targetMarker: { + 'type': 'path', + 'd': 'M 0 -10 -10 0 0 10 z', + 'stroke': 'none' + }, + sourceMarker: { + 'type': 'path', + 'stroke': 'none', + 'd': 'M -10 -10 0 0 -10 10 0 10 0 -10 z' + } + } + } + }, { + markup: [{ + tagName: 'path', + selector: 'shadow', + attributes: { + 'fill': 'none' + } + }, { + tagName: 'path', + selector: 'line', + attributes: { + 'fill': 'none' + } + }] + }); + + +})(joint.dia, joint.util, joint.env, V); + +joint.routers.manhattan = (function(g, _, joint, util) { + + 'use strict'; + + var config = { + + // size of the step to find a route (the grid of the manhattan pathfinder) + step: 10, + + // the number of route finding loops that cause the router to abort + // returns fallback route instead + maximumLoops: 2000, + + // the number of decimal places to round floating point coordinates + precision: 10, + + // maximum change of direction + maxAllowedDirectionChange: 90, + + // should the router use perpendicular linkView option? + // does not connect anchor of element but rather a point close-by that is orthogonal + // this looks much better + perpendicular: true, + + // should the source and/or target not be considered as obstacles? + excludeEnds: [], // 'source', 'target' + + // should certain types of elements not be considered as obstacles? + excludeTypes: ['basic.Text'], + + // possible starting directions from an element + startDirections: ['top', 'right', 'bottom', 'left'], + + // possible ending directions to an element + endDirections: ['top', 'right', 'bottom', 'left'], + + // specify the directions used above and what they mean + directionMap: { + top: { x: 0, y: -1 }, + right: { x: 1, y: 0 }, + bottom: { x: 0, y: 1 }, + left: { x: -1, y: 0 } + }, + + // cost of an orthogonal step + cost: function() { + + return this.step; + }, + + // an array of directions to find next points on the route + // different from start/end directions + directions: function() { + + var step = this.step; + var cost = this.cost(); + + return [ + { offsetX: step , offsetY: 0 , cost: cost }, + { offsetX: 0 , offsetY: step , cost: cost }, + { offsetX: -step , offsetY: 0 , cost: cost }, + { offsetX: 0 , offsetY: -step , cost: cost } + ]; + }, + + // a penalty received for direction change + penalties: function() { + + return { + 0: 0, + 45: this.step / 2, + 90: this.step / 2 + }; + }, + + // padding applied on the element bounding boxes + paddingBox: function() { + + var step = this.step; + + return { + x: -step, + y: -step, + width: 2 * step, + height: 2 * step + }; + }, + + // a router to use when the manhattan router fails + // (one of the partial routes returns null) + fallbackRouter: function(vertices, opt, linkView) { + + if (!util.isFunction(joint.routers.orthogonal)) { + throw new Error('Manhattan requires the orthogonal router as default fallback.'); + } + + return joint.routers.orthogonal(vertices, util.assign({}, config, opt), linkView); + }, + + /* Deprecated */ + // a simple route used in situations when main routing method fails + // (exceed max number of loop iterations, inaccessible) + fallbackRoute: function(from, to, opt) { + + return null; // null result will trigger the fallbackRouter + + // left for reference: + /*// Find an orthogonal route ignoring obstacles. + + var point = ((opt.previousDirAngle || 0) % 180 === 0) + ? new g.Point(from.x, to.y) + : new g.Point(to.x, from.y); + + return [point];*/ + }, + + // if a function is provided, it's used to route the link while dragging an end + // i.e. function(from, to, opt) { return []; } + draggingRoute: null + }; + + // HELPER CLASSES // + + // Map of obstacles + // Helper structure to identify whether a point lies inside an obstacle. + function ObstacleMap(opt) { + + this.map = {}; + this.options = opt; + // tells how to divide the paper when creating the elements map + this.mapGridSize = 100; + } + + ObstacleMap.prototype.build = function(graph, link) { + + var opt = this.options; + + // source or target element could be excluded from set of obstacles + var excludedEnds = util.toArray(opt.excludeEnds).reduce(function(res, item) { + + var end = link.get(item); + if (end) { + var cell = graph.getCell(end.id); + if (cell) { + res.push(cell); + } + } + + return res; + }, []); + + // Exclude any embedded elements from the source and the target element. + var excludedAncestors = []; + + var source = graph.getCell(link.get('source').id); + if (source) { + excludedAncestors = util.union(excludedAncestors, source.getAncestors().map(function(cell) { return cell.id })); + } + + var target = graph.getCell(link.get('target').id); + if (target) { + excludedAncestors = util.union(excludedAncestors, target.getAncestors().map(function(cell) { return cell.id })); + } + + // Builds a map of all elements for quicker obstacle queries (i.e. is a point contained + // in any obstacle?) (a simplified grid search). + // The paper is divided into smaller cells, where each holds information about which + // elements belong to it. When we query whether a point lies inside an obstacle we + // don't need to go through all obstacles, we check only those in a particular cell. + var mapGridSize = this.mapGridSize; + + graph.getElements().reduce(function(map, element) { + + var isExcludedType = util.toArray(opt.excludeTypes).includes(element.get('type')); + var isExcludedEnd = excludedEnds.find(function(excluded) { return excluded.id === element.id }); + var isExcludedAncestor = excludedAncestors.includes(element.id); + + var isExcluded = isExcludedType || isExcludedEnd || isExcludedAncestor; + if (!isExcluded) { + var bbox = element.getBBox().moveAndExpand(opt.paddingBox); + + var origin = bbox.origin().snapToGrid(mapGridSize); + var corner = bbox.corner().snapToGrid(mapGridSize); + + for (var x = origin.x; x <= corner.x; x += mapGridSize) { + for (var y = origin.y; y <= corner.y; y += mapGridSize) { + var gridKey = x + '@' + y; + map[gridKey] = map[gridKey] || []; + map[gridKey].push(bbox); + } + } + } + + return map; + }, this.map); + + return this; + }; + + ObstacleMap.prototype.isPointAccessible = function(point) { + + var mapKey = point.clone().snapToGrid(this.mapGridSize).toString(); + + return util.toArray(this.map[mapKey]).every( function(obstacle) { + return !obstacle.containsPoint(point); + }); + }; + + // Sorted Set + // Set of items sorted by given value. + function SortedSet() { + this.items = []; + this.hash = {}; + this.values = {}; + this.OPEN = 1; + this.CLOSE = 2; + } + + SortedSet.prototype.add = function(item, value) { + + if (this.hash[item]) { + // item removal + this.items.splice(this.items.indexOf(item), 1); + } else { + this.hash[item] = this.OPEN; + } + + this.values[item] = value; + + var index = joint.util.sortedIndex(this.items, item, function(i) { + return this.values[i]; + }.bind(this)); + + this.items.splice(index, 0, item); + }; + + SortedSet.prototype.remove = function(item) { + + this.hash[item] = this.CLOSE; + }; + + SortedSet.prototype.isOpen = function(item) { + + return this.hash[item] === this.OPEN; + }; + + SortedSet.prototype.isClose = function(item) { + + return this.hash[item] === this.CLOSE; + }; + + SortedSet.prototype.isEmpty = function() { + + return this.items.length === 0; + }; + + SortedSet.prototype.pop = function() { + + var item = this.items.shift(); + this.remove(item); + return item; + }; + + // HELPERS // + + // return source bbox + function getSourceBBox(linkView, opt) { + + // expand by padding box + if (opt && opt.paddingBox) return linkView.sourceBBox.clone().moveAndExpand(opt.paddingBox); + + return linkView.sourceBBox.clone(); + } + + // return target bbox + function getTargetBBox(linkView, opt) { + + // expand by padding box + if (opt && opt.paddingBox) return linkView.targetBBox.clone().moveAndExpand(opt.paddingBox); + + return linkView.targetBBox.clone(); + } + + // return source anchor + function getSourceAnchor(linkView, opt) { + + if (linkView.sourceAnchor) return linkView.sourceAnchor; + + // fallback: center of bbox + var sourceBBox = getSourceBBox(linkView, opt); + return sourceBBox.center(); + } + + // return target anchor + function getTargetAnchor(linkView, opt) { + + if (linkView.targetAnchor) return linkView.targetAnchor; + + // fallback: center of bbox + var targetBBox = getTargetBBox(linkView, opt); + return targetBBox.center(); // default + } + + // returns a direction index from start point to end point + // corrects for grid deformation between start and end + function getDirectionAngle(start, end, numDirections, grid, opt) { + + var quadrant = 360 / numDirections; + var angleTheta = start.theta(fixAngleEnd(start, end, grid, opt)); + var normalizedAngle = g.normalizeAngle(angleTheta + (quadrant / 2)); + return quadrant * Math.floor(normalizedAngle / quadrant); + } + + // helper function for getDirectionAngle() + // corrects for grid deformation + // (if a point is one grid steps away from another in both dimensions, + // it is considered to be 45 degrees away, even if the real angle is different) + // this causes visible angle discrepancies if `opt.step` is much larger than `paper.gridSize` + function fixAngleEnd(start, end, grid, opt) { + + var step = opt.step; + + var diffX = end.x - start.x; + var diffY = end.y - start.y; + + var gridStepsX = diffX / grid.x; + var gridStepsY = diffY / grid.y + + var distanceX = gridStepsX * step; + var distanceY = gridStepsY * step; + + return new g.Point(start.x + distanceX, start.y + distanceY); + } + + // return the change in direction between two direction angles + function getDirectionChange(angle1, angle2) { + + var directionChange = Math.abs(angle1 - angle2); + return (directionChange > 180) ? (360 - directionChange) : directionChange; + } + + // fix direction offsets according to current grid + function getGridOffsets(directions, grid, opt) { + + var step = opt.step; + + util.toArray(opt.directions).forEach(function(direction) { + + direction.gridOffsetX = (direction.offsetX / step) * grid.x; + direction.gridOffsetY = (direction.offsetY / step) * grid.y; + }); + } + + // get grid size in x and y dimensions, adapted to source and target positions + function getGrid(step, source, target) { + + return { + source: source.clone(), + x: getGridDimension(target.x - source.x, step), + y: getGridDimension(target.y - source.y, step) + } + } + + // helper function for getGrid() + function getGridDimension(diff, step) { + + // return step if diff = 0 + if (!diff) return step; + + var absDiff = Math.abs(diff); + var numSteps = Math.round(absDiff / step); + + // return absDiff if less than one step apart + if (!numSteps) return absDiff; + + // otherwise, return corrected step + var roundedDiff = numSteps * step; + var remainder = absDiff - roundedDiff; + var stepCorrection = remainder / numSteps; + + return step + stepCorrection; + } + + // return a clone of point snapped to grid + function snapToGrid(point, grid) { + + var source = grid.source; + + var snappedX = g.snapToGrid(point.x - source.x, grid.x) + source.x; + var snappedY = g.snapToGrid(point.y - source.y, grid.y) + source.y; + + return new g.Point(snappedX, snappedY); + } + + // round the point to opt.precision + function round(point, opt) { + + if (!point) return point; + + return point.round(opt.precision); + } + + // return a string representing the point + // string is rounded to nearest int in both dimensions + function getKey(point) { + + return point.clone().round().toString(); + } + + // return a normalized vector from given point + // used to determine the direction of a difference of two points + function normalizePoint(point) { + + return new g.Point( + point.x === 0 ? 0 : Math.abs(point.x) / point.x, + point.y === 0 ? 0 : Math.abs(point.y) / point.y + ); + } + + // PATHFINDING // + + // reconstructs a route by concatenating points with their parents + function reconstructRoute(parents, points, tailPoint, from, to, opt) { + + var route = []; + + var prevDiff = normalizePoint(to.difference(tailPoint)); + + var currentKey = getKey(tailPoint); + var parent = parents[currentKey]; + + var point; + while (parent) { + + point = round(points[currentKey], opt); + + var diff = normalizePoint(point.difference(round(parent.clone(), opt))); + if (!diff.equals(prevDiff)) { + route.unshift(point); + prevDiff = diff; + } + + currentKey = getKey(parent); + parent = parents[currentKey]; + } + + var leadPoint = round(points[currentKey], opt); + + var fromDiff = normalizePoint(leadPoint.difference(from)); + if (!fromDiff.equals(prevDiff)) { + route.unshift(leadPoint); + } + + return route; + } + + // heuristic method to determine the distance between two points + function estimateCost(from, endPoints) { + + var min = Infinity; + + for (var i = 0, len = endPoints.length; i < len; i++) { + var cost = from.manhattanDistance(endPoints[i]); + if (cost < min) min = cost; + } + + return min; + } + + // find points around the bbox taking given directions into account + // lines are drawn from anchor in given directions, intersections recorded + // if anchor is outside bbox, only those directions that intersect get a rect point + // the anchor itself is returned as rect point (representing some directions) + // (since those directions are unobstructed by the bbox) + function getRectPoints(anchor, bbox, directionList, grid, opt) { + + var directionMap = opt.directionMap; + + var snappedAnchor = round(snapToGrid(anchor, grid), opt); + var snappedCenter = round(snapToGrid(bbox.center(), grid), opt); + var anchorCenterVector = snappedAnchor.difference(snappedCenter); + + var keys = util.isObject(directionMap) ? Object.keys(directionMap) : []; + var dirList = util.toArray(directionList); + var rectPoints = keys.reduce(function(res, key) { + + if (dirList.includes(key)) { + var direction = directionMap[key]; + + // create a line that is guaranteed to intersect the bbox if bbox is in the direction + // even if anchor lies outside of bbox + var endpoint = new g.Point( + snappedAnchor.x + direction.x * (Math.abs(anchorCenterVector.x) + bbox.width), + snappedAnchor.y + direction.y * (Math.abs(anchorCenterVector.y) + bbox.height) + ); + var intersectionLine = new g.Line(anchor, endpoint); + + // get the farther intersection, in case there are two + // (that happens if anchor lies next to bbox) + var intersections = intersectionLine.intersect(bbox) || []; + var numIntersections = intersections.length; + var farthestIntersectionDistance; + var farthestIntersection = null; + for (var i = 0; i < numIntersections; i++) { + var currentIntersection = intersections[i]; + var distance = snappedAnchor.squaredDistance(currentIntersection); + if (farthestIntersectionDistance === undefined || (distance > farthestIntersectionDistance)) { + farthestIntersectionDistance = distance; + farthestIntersection = snapToGrid(currentIntersection, grid); + } + } + var point = round(farthestIntersection, opt); + + // if an intersection was found in this direction, it is our rectPoint + if (point) { + // if the rectPoint lies inside the bbox, offset it by one more step + if (bbox.containsPoint(point)) { + round(point.offset(direction.x * grid.x, direction.y * grid.y), opt); + } + + // then add the point to the result array + res.push(point); + } + } + + return res; + }, []); + + // if anchor lies outside of bbox, add it to the array of points + if (!bbox.containsPoint(snappedAnchor)) rectPoints.push(snappedAnchor); + + return rectPoints; + } + + // finds the route between two points/rectangles (`from`, `to`) implementing A* algorithm + // rectangles get rect points assigned by getRectPoints() + function findRoute(from, to, map, opt) { + + // Get grid for this route. + + var sourceAnchor, targetAnchor; + + if (from instanceof g.Rect) { // `from` is sourceBBox + sourceAnchor = getSourceAnchor(this, opt).clone(); + } else { + sourceAnchor = from.clone(); + } + + if (to instanceof g.Rect) { // `to` is targetBBox + targetAnchor = getTargetAnchor(this, opt).clone(); + } else { + targetAnchor = to.clone(); + } + + var grid = getGrid(opt.step, sourceAnchor, targetAnchor); + + // Get pathfinding points. + + var start, end; + var startPoints, endPoints; + + // set of points we start pathfinding from + if (from instanceof g.Rect) { // `from` is sourceBBox + start = round(snapToGrid(sourceAnchor, grid), opt); + startPoints = getRectPoints(start, from, opt.startDirections, grid, opt); + + } else { + start = round(snapToGrid(sourceAnchor, grid), opt); + startPoints = [start]; + } + + // set of points we want the pathfinding to finish at + if (to instanceof g.Rect) { // `to` is targetBBox + end = round(snapToGrid(targetAnchor, grid), opt); + endPoints = getRectPoints(targetAnchor, to, opt.endDirections, grid, opt); + + } else { + end = round(snapToGrid(targetAnchor, grid), opt); + endPoints = [end]; + } + + // take into account only accessible rect points (those not under obstacles) + startPoints = startPoints.filter(map.isPointAccessible, map); + endPoints = endPoints.filter(map.isPointAccessible, map); + + // Check that there is an accessible route point on both sides. + // Otherwise, use fallbackRoute(). + if (startPoints.length > 0 && endPoints.length > 0) { + + // The set of tentative points to be evaluated, initially containing the start points. + // Rounded to nearest integer for simplicity. + var openSet = new SortedSet(); + // Keeps reference to actual points for given elements of the open set. + var points = {}; + // Keeps reference to a point that is immediate predecessor of given element. + var parents = {}; + // Cost from start to a point along best known path. + var costs = {}; + + for (var i = 0, n = startPoints.length; i < n; i++) { + var point = startPoints[i]; + + var key = getKey(point); + openSet.add(key, estimateCost(point, endPoints)); + points[key] = point; + costs[key] = 0; + } + + var previousRouteDirectionAngle = opt.previousDirectionAngle; // undefined for first route + var isPathBeginning = (previousRouteDirectionAngle === undefined); + + // directions + var direction, directionChange; + var directions = opt.directions; + getGridOffsets(directions, grid, opt); + + var numDirections = directions.length; + + var endPointsKeys = util.toArray(endPoints).reduce(function(res, endPoint) { + + var key = getKey(endPoint); + res.push(key); + return res; + }, []); + + // main route finding loop + var loopsRemaining = opt.maximumLoops; + while (!openSet.isEmpty() && loopsRemaining > 0) { + + // remove current from the open list + var currentKey = openSet.pop(); + var currentPoint = points[currentKey]; + var currentParent = parents[currentKey]; + var currentCost = costs[currentKey]; + + var isRouteBeginning = (currentParent === undefined); // undefined for route starts + var isStart = currentPoint.equals(start); // (is source anchor or `from` point) = can leave in any direction + + var previousDirectionAngle; + if (!isRouteBeginning) previousDirectionAngle = getDirectionAngle(currentParent, currentPoint, numDirections, grid, opt); // a vertex on the route + else if (!isPathBeginning) previousDirectionAngle = previousRouteDirectionAngle; // beginning of route on the path + else if (!isStart) previousDirectionAngle = getDirectionAngle(start, currentPoint, numDirections, grid, opt); // beginning of path, start rect point + else previousDirectionAngle = null; // beginning of path, source anchor or `from` point + + // check if we reached any endpoint + if (endPointsKeys.indexOf(currentKey) >= 0) { + opt.previousDirectionAngle = previousDirectionAngle; + return reconstructRoute(parents, points, currentPoint, start, end, opt); + } + + // go over all possible directions and find neighbors + for (i = 0; i < numDirections; i++) { + direction = directions[i]; + + var directionAngle = direction.angle; + directionChange = getDirectionChange(previousDirectionAngle, directionAngle); + + // if the direction changed rapidly, don't use this point + // any direction is allowed for starting points + if (!(isPathBeginning && isStart) && directionChange > opt.maxAllowedDirectionChange) continue; + + var neighborPoint = currentPoint.clone().offset(direction.gridOffsetX, direction.gridOffsetY); + var neighborKey = getKey(neighborPoint); + + // Closed points from the openSet were already evaluated. + if (openSet.isClose(neighborKey) || !map.isPointAccessible(neighborPoint)) continue; + + // We can only enter end points at an acceptable angle. + if (endPointsKeys.indexOf(neighborKey) >= 0) { // neighbor is an end point + round(neighborPoint, opt); // remove rounding errors + + var isNeighborEnd = neighborPoint.equals(end); // (is target anchor or `to` point) = can be entered in any direction + + if (!isNeighborEnd) { + var endDirectionAngle = getDirectionAngle(neighborPoint, end, numDirections, grid, opt); + var endDirectionChange = getDirectionChange(directionAngle, endDirectionAngle); + + if (endDirectionChange > opt.maxAllowedDirectionChange) continue; + } + } + + // The current direction is ok. + + var neighborCost = direction.cost; + var neighborPenalty = isStart ? 0 : opt.penalties[directionChange]; // no penalties for start point + var costFromStart = currentCost + neighborCost + neighborPenalty; + + if (!openSet.isOpen(neighborKey) || (costFromStart < costs[neighborKey])) { + // neighbor point has not been processed yet + // or the cost of the path from start is lower than previously calculated + + points[neighborKey] = neighborPoint; + parents[neighborKey] = currentPoint; + costs[neighborKey] = costFromStart; + openSet.add(neighborKey, costFromStart + estimateCost(neighborPoint, endPoints)); + } + } + + loopsRemaining--; + } + } + + // no route found (`to` point either wasn't accessible or finding route took + // way too much calculation) + return opt.fallbackRoute.call(this, start, end, opt); + } + + // resolve some of the options + function resolveOptions(opt) { + + opt.directions = util.result(opt, 'directions'); + opt.penalties = util.result(opt, 'penalties'); + opt.paddingBox = util.result(opt, 'paddingBox'); + + util.toArray(opt.directions).forEach(function(direction) { + + var point1 = new g.Point(0, 0); + var point2 = new g.Point(direction.offsetX, direction.offsetY); + + direction.angle = g.normalizeAngle(point1.theta(point2)); + }); + } + + // initialization of the route finding + function router(vertices, opt, linkView) { + + resolveOptions(opt); + + // enable/disable linkView perpendicular option + linkView.options.perpendicular = !!opt.perpendicular; + + var sourceBBox = getSourceBBox(linkView, opt); + var targetBBox = getTargetBBox(linkView, opt); + + var sourceAnchor = getSourceAnchor(linkView, opt); + //var targetAnchor = getTargetAnchor(linkView, opt); + + // pathfinding + var map = (new ObstacleMap(opt)).build(linkView.paper.model, linkView.model); + var oldVertices = util.toArray(vertices).map(g.Point); + var newVertices = []; + var tailPoint = sourceAnchor; // the origin of first route's grid, does not need snapping + + // find a route by concatenating all partial routes (routes need to pass through vertices) + // source -> vertex[1] -> ... -> vertex[n] -> target + for (var i = 0, len = oldVertices.length; i <= len; i++) { + + var partialRoute = null; + + var from = to || sourceBBox; + var to = oldVertices[i]; + + if (!to) { + // this is the last iteration + // we ran through all vertices in oldVertices + // 'to' is not a vertex. + + to = targetBBox; + + // If the target is a point (i.e. it's not an element), we + // should use dragging route instead of main routing method if it has been provided. + var isEndingAtPoint = !linkView.model.get('source').id || !linkView.model.get('target').id; + + if (isEndingAtPoint && util.isFunction(opt.draggingRoute)) { + // Make sure we are passing points only (not rects). + var dragFrom = (from === sourceBBox) ? sourceAnchor : from; + var dragTo = to.origin(); + + partialRoute = opt.draggingRoute.call(linkView, dragFrom, dragTo, opt); + } + } + + // if partial route has not been calculated yet use the main routing method to find one + partialRoute = partialRoute || findRoute.call(linkView, from, to, map, opt); + + if (partialRoute === null) { // the partial route cannot be found + return opt.fallbackRouter(vertices, opt, linkView); + } + + var leadPoint = partialRoute[0]; + + // remove the first point if the previous partial route had the same point as last + if (leadPoint && leadPoint.equals(tailPoint)) partialRoute.shift(); + + // save tailPoint for next iteration + tailPoint = partialRoute[partialRoute.length - 1] || tailPoint; + + Array.prototype.push.apply(newVertices, partialRoute); + } + + return newVertices; + } + + // public function + return function(vertices, opt, linkView) { + + return router(vertices, util.assign({}, config, opt), linkView); + }; + +})(g, _, joint, joint.util); + +joint.routers.metro = (function(util) { + + var config = { + + maxAllowedDirectionChange: 45, + + // cost of a diagonal step + diagonalCost: function() { + + var step = this.step; + return Math.ceil(Math.sqrt(step * step << 1)); + }, + + // an array of directions to find next points on the route + // different from start/end directions + directions: function() { + + var step = this.step; + var cost = this.cost(); + var diagonalCost = this.diagonalCost(); + + return [ + { offsetX: step , offsetY: 0 , cost: cost }, + { offsetX: step , offsetY: step , cost: diagonalCost }, + { offsetX: 0 , offsetY: step , cost: cost }, + { offsetX: -step , offsetY: step , cost: diagonalCost }, + { offsetX: -step , offsetY: 0 , cost: cost }, + { offsetX: -step , offsetY: -step , cost: diagonalCost }, + { offsetX: 0 , offsetY: -step , cost: cost }, + { offsetX: step , offsetY: -step , cost: diagonalCost } + ]; + }, + + // a simple route used in situations when main routing method fails + // (exceed max number of loop iterations, inaccessible) + fallbackRoute: function(from, to, opt) { + + // Find a route which breaks by 45 degrees ignoring all obstacles. + + var theta = from.theta(to); + + var route = []; + + var a = { x: to.x, y: from.y }; + var b = { x: from.x, y: to.y }; + + if (theta % 180 > 90) { + var t = a; + a = b; + b = t; + } + + var p1 = (theta % 90) < 45 ? a : b; + var l1 = new g.Line(from, p1); + + var alpha = 90 * Math.ceil(theta / 90); + + var p2 = g.Point.fromPolar(l1.squaredLength(), g.toRad(alpha + 135), p1); + var l2 = new g.Line(to, p2); + + var intersectionPoint = l1.intersection(l2); + var point = intersectionPoint ? intersectionPoint : to; + + var directionFrom = intersectionPoint ? point : from; + + var quadrant = 360 / opt.directions.length; + var angleTheta = directionFrom.theta(to); + var normalizedAngle = g.normalizeAngle(angleTheta + (quadrant / 2)); + var directionAngle = quadrant * Math.floor(normalizedAngle / quadrant); + + opt.previousDirectionAngle = directionAngle; + + if (point) route.push(point.round()); + route.push(to); + + return route; + } + }; + + // public function + return function(vertices, opt, linkView) { + + if (!util.isFunction(joint.routers.manhattan)) { + throw new Error('Metro requires the manhattan router.'); + } + + return joint.routers.manhattan(vertices, util.assign({}, config, opt), linkView); + }; + +})(joint.util); + +// Does not make any changes to vertices. +// Returns the arguments that are passed to it, unchanged. +joint.routers.normal = function(vertices, opt, linkView) { + + return vertices; +}; + +// Routes the link always to/from a certain side +// +// Arguments: +// padding ... gap between the element and the first vertex. :: Default 40. +// side ... 'left' | 'right' | 'top' | 'bottom' :: Default 'bottom'. +// +joint.routers.oneSide = function(vertices, opt, linkView) { + + var side = opt.side || 'bottom'; + var padding = opt.padding || 40; + + // LinkView contains cached source an target bboxes. + // Note that those are Geometry rectangle objects. + var sourceBBox = linkView.sourceBBox; + var targetBBox = linkView.targetBBox; + var sourcePoint = sourceBBox.center(); + var targetPoint = targetBBox.center(); + + var coordinate, dimension, direction; + + switch (side) { + case 'bottom': + direction = 1; + coordinate = 'y'; + dimension = 'height'; + break; + case 'top': + direction = -1; + coordinate = 'y'; + dimension = 'height'; + break; + case 'left': + direction = -1; + coordinate = 'x'; + dimension = 'width'; + break; + case 'right': + direction = 1; + coordinate = 'x'; + dimension = 'width'; + break; + default: + throw new Error('Router: invalid side'); + } + + // move the points from the center of the element to outside of it. + sourcePoint[coordinate] += direction * (sourceBBox[dimension] / 2 + padding); + targetPoint[coordinate] += direction * (targetBBox[dimension] / 2 + padding); + + // make link orthogonal (at least the first and last vertex). + if (direction * (sourcePoint[coordinate] - targetPoint[coordinate]) > 0) { + targetPoint[coordinate] = sourcePoint[coordinate]; + } else { + sourcePoint[coordinate] = targetPoint[coordinate]; + } + + return [sourcePoint].concat(vertices, targetPoint); +}; + +joint.routers.orthogonal = (function(util) { + + // bearing -> opposite bearing + var opposites = { + N: 'S', + S: 'N', + E: 'W', + W: 'E' + }; + + // bearing -> radians + var radians = { + N: -Math.PI / 2 * 3, + S: -Math.PI / 2, + E: 0, + W: Math.PI + }; + + // HELPERS // + + // returns a point `p` where lines p,p1 and p,p2 are perpendicular and p is not contained + // in the given box + function freeJoin(p1, p2, bbox) { + + var p = new g.Point(p1.x, p2.y); + if (bbox.containsPoint(p)) p = new g.Point(p2.x, p1.y); + // kept for reference + // if (bbox.containsPoint(p)) p = null; + + return p; + } + + // returns either width or height of a bbox based on the given bearing + function getBBoxSize(bbox, bearing) { + + return bbox[(bearing === 'W' || bearing === 'E') ? 'width' : 'height']; + } + + // simple bearing method (calculates only orthogonal cardinals) + function getBearing(from, to) { + + if (from.x === to.x) return (from.y > to.y) ? 'N' : 'S'; + if (from.y === to.y) return (from.x > to.x) ? 'W' : 'E'; + return null; + } + + // transform point to a rect + function getPointBox(p) { + + return new g.Rect(p.x, p.y, 0, 0); + } + + // return source bbox + function getSourceBBox(linkView, opt) { + + var padding = (opt && opt.elementPadding) || 20; + return linkView.sourceBBox.clone().inflate(padding); + } + + // return target bbox + function getTargetBBox(linkView, opt) { + + var padding = (opt && opt.elementPadding) || 20; + return linkView.targetBBox.clone().inflate(padding); + } + + // return source anchor + function getSourceAnchor(linkView, opt) { + + if (linkView.sourceAnchor) return linkView.sourceAnchor; + + // fallback: center of bbox + var sourceBBox = getSourceBBox(linkView, opt); + return sourceBBox.center(); + } + + // return target anchor + function getTargetAnchor(linkView, opt) { + + if (linkView.targetAnchor) return linkView.targetAnchor; + + // fallback: center of bbox + var targetBBox = getTargetBBox(linkView, opt); + return targetBBox.center(); // default + } + + // PARTIAL ROUTERS // + + function vertexVertex(from, to, bearing) { + + var p1 = new g.Point(from.x, to.y); + var p2 = new g.Point(to.x, from.y); + var d1 = getBearing(from, p1); + var d2 = getBearing(from, p2); + var opposite = opposites[bearing]; + + var p = (d1 === bearing || (d1 !== opposite && (d2 === opposite || d2 !== bearing))) ? p1 : p2; + + return { points: [p], direction: getBearing(p, to) }; + } + + function elementVertex(from, to, fromBBox) { + + var p = freeJoin(from, to, fromBBox); + + return { points: [p], direction: getBearing(p, to) }; + } + + function vertexElement(from, to, toBBox, bearing) { + + var route = {}; + + var points = [new g.Point(from.x, to.y), new g.Point(to.x, from.y)]; + var freePoints = points.filter(function(pt) { return !toBBox.containsPoint(pt); }); + var freeBearingPoints = freePoints.filter(function(pt) { return getBearing(pt, from) !== bearing; }); + + var p; + + if (freeBearingPoints.length > 0) { + // Try to pick a point which bears the same direction as the previous segment. + + p = freeBearingPoints.filter(function(pt) { return getBearing(from, pt) === bearing; }).pop(); + p = p || freeBearingPoints[0]; + + route.points = [p]; + route.direction = getBearing(p, to); + + } else { + // Here we found only points which are either contained in the element or they would create + // a link segment going in opposite direction from the previous one. + // We take the point inside element and move it outside the element in the direction the + // route is going. Now we can join this point with the current end (using freeJoin). + + p = util.difference(points, freePoints)[0]; + + var p2 = (new g.Point(to)).move(p, -getBBoxSize(toBBox, bearing) / 2); + var p1 = freeJoin(p2, from, toBBox); + + route.points = [p1, p2]; + route.direction = getBearing(p2, to); + } + + return route; + } + + function elementElement(from, to, fromBBox, toBBox) { + + var route = elementVertex(to, from, toBBox); + var p1 = route.points[0]; + + if (fromBBox.containsPoint(p1)) { + + route = elementVertex(from, to, fromBBox); + var p2 = route.points[0]; + + if (toBBox.containsPoint(p2)) { + + var fromBorder = (new g.Point(from)).move(p2, -getBBoxSize(fromBBox, getBearing(from, p2)) / 2); + var toBorder = (new g.Point(to)).move(p1, -getBBoxSize(toBBox, getBearing(to, p1)) / 2); + var mid = (new g.Line(fromBorder, toBorder)).midpoint(); + + var startRoute = elementVertex(from, mid, fromBBox); + var endRoute = vertexVertex(mid, to, startRoute.direction); + + route.points = [startRoute.points[0], endRoute.points[0]]; + route.direction = endRoute.direction; + } + } + + return route; + } + + // Finds route for situations where one element is inside the other. + // Typically the route is directed outside the outer element first and + // then back towards the inner element. + function insideElement(from, to, fromBBox, toBBox, bearing) { + + var route = {}; + var boundary = fromBBox.union(toBBox).inflate(1); + + // start from the point which is closer to the boundary + var reversed = boundary.center().distance(to) > boundary.center().distance(from); + var start = reversed ? to : from; + var end = reversed ? from : to; + + var p1, p2, p3; + + if (bearing) { + // Points on circle with radius equals 'W + H` are always outside the rectangle + // with width W and height H if the center of that circle is the center of that rectangle. + p1 = g.Point.fromPolar(boundary.width + boundary.height, radians[bearing], start); + p1 = boundary.pointNearestToPoint(p1).move(p1, -1); + + } else { + p1 = boundary.pointNearestToPoint(start).move(start, 1); + } + + p2 = freeJoin(p1, end, boundary); + + if (p1.round().equals(p2.round())) { + p2 = g.Point.fromPolar(boundary.width + boundary.height, g.toRad(p1.theta(start)) + Math.PI / 2, end); + p2 = boundary.pointNearestToPoint(p2).move(end, 1).round(); + p3 = freeJoin(p1, p2, boundary); + route.points = reversed ? [p2, p3, p1] : [p1, p3, p2]; + + } else { + route.points = reversed ? [p2, p1] : [p1, p2]; + } + + route.direction = reversed ? getBearing(p1, to) : getBearing(p2, to); + + return route; + } + + // MAIN ROUTER // + + // Return points through which a connection needs to be drawn in order to obtain an orthogonal link + // routing from source to target going through `vertices`. + function router(vertices, opt, linkView) { + + var padding = opt.elementPadding || 20; + + var sourceBBox = getSourceBBox(linkView, opt); + var targetBBox = getTargetBBox(linkView, opt); + + var sourceAnchor = getSourceAnchor(linkView, opt); + var targetAnchor = getTargetAnchor(linkView, opt); + + // if anchor lies outside of bbox, the bbox expands to include it + sourceBBox = sourceBBox.union(getPointBox(sourceAnchor)); + targetBBox = targetBBox.union(getPointBox(targetAnchor)); + + vertices = util.toArray(vertices).map(g.Point); + vertices.unshift(sourceAnchor); + vertices.push(targetAnchor); + + var bearing; // bearing of previous route segment + + var orthogonalVertices = []; // the array of found orthogonal vertices to be returned + for (var i = 0, max = vertices.length - 1; i < max; i++) { + + var route = null; + + var from = vertices[i]; + var to = vertices[i + 1]; + + var isOrthogonal = !!getBearing(from, to); + + if (i === 0) { // source + + if (i + 1 === max) { // route source -> target + + // Expand one of the elements by 1px to detect situations when the two + // elements are positioned next to each other with no gap in between. + if (sourceBBox.intersect(targetBBox.clone().inflate(1))) { + route = insideElement(from, to, sourceBBox, targetBBox); + + } else if (!isOrthogonal) { + route = elementElement(from, to, sourceBBox, targetBBox); + } + + } else { // route source -> vertex + + if (sourceBBox.containsPoint(to)) { + route = insideElement(from, to, sourceBBox, getPointBox(to).inflate(padding)); + + } else if (!isOrthogonal) { + route = elementVertex(from, to, sourceBBox); + } + } + + } else if (i + 1 === max) { // route vertex -> target + + // prevent overlaps with previous line segment + var isOrthogonalLoop = isOrthogonal && getBearing(to, from) === bearing; + + if (targetBBox.containsPoint(from) || isOrthogonalLoop) { + route = insideElement(from, to, getPointBox(from).inflate(padding), targetBBox, bearing); + + } else if (!isOrthogonal) { + route = vertexElement(from, to, targetBBox, bearing); + } + + } else if (!isOrthogonal) { // route vertex -> vertex + route = vertexVertex(from, to, bearing); + } + + // applicable to all routes: + + // set bearing for next iteration + if (route) { + Array.prototype.push.apply(orthogonalVertices, route.points); + bearing = route.direction; + + } else { + // orthogonal route and not looped + bearing = getBearing(from, to); + } + + // push `to` point to identified orthogonal vertices array + if (i + 1 < max) { + orthogonalVertices.push(to); + } + } + + return orthogonalVertices; + } + + return router; + +})(joint.util); + +joint.connectors.normal = function(sourcePoint, targetPoint, route, opt) { + + var raw = opt && opt.raw; + var points = [sourcePoint].concat(route).concat([targetPoint]); + + var polyline = new g.Polyline(points); + var path = new g.Path(polyline); + + return (raw) ? path : path.serialize(); +}; + +joint.connectors.rounded = function(sourcePoint, targetPoint, route, opt) { + + opt || (opt = {}); + + var offset = opt.radius || 10; + var raw = opt.raw; + var path = new g.Path(); + var segment; + + segment = g.Path.createSegment('M', sourcePoint); + path.appendSegment(segment); + + var _13 = 1 / 3; + var _23 = 2 / 3; + + var curr; + var prev, next; + var prevDistance, nextDistance; + var startMove, endMove; + var roundedStart, roundedEnd; + var control1, control2; + + for (var index = 0, n = route.length; index < n; index++) { + + curr = new g.Point(route[index]); + + prev = route[index - 1] || sourcePoint; + next = route[index + 1] || targetPoint; + + prevDistance = nextDistance || (curr.distance(prev) / 2); + nextDistance = curr.distance(next) / 2; + + startMove = -Math.min(offset, prevDistance); + endMove = -Math.min(offset, nextDistance); + + roundedStart = curr.clone().move(prev, startMove).round(); + roundedEnd = curr.clone().move(next, endMove).round(); + + control1 = new g.Point((_13 * roundedStart.x) + (_23 * curr.x), (_23 * curr.y) + (_13 * roundedStart.y)); + control2 = new g.Point((_13 * roundedEnd.x) + (_23 * curr.x), (_23 * curr.y) + (_13 * roundedEnd.y)); + + segment = g.Path.createSegment('L', roundedStart); + path.appendSegment(segment); + + segment = g.Path.createSegment('C', control1, control2, roundedEnd); + path.appendSegment(segment); + } + + segment = g.Path.createSegment('L', targetPoint); + path.appendSegment(segment); + + return (raw) ? path : path.serialize(); +}; + +joint.connectors.smooth = function(sourcePoint, targetPoint, route, opt) { + + var raw = opt && opt.raw; + var path; + + if (route && route.length !== 0) { + + var points = [sourcePoint].concat(route).concat([targetPoint]); + var curves = g.Curve.throughPoints(points); + + path = new g.Path(curves); + + } else { + // if we have no route, use a default cubic bezier curve + // cubic bezier requires two control points + // the control points have `x` midway between source and target + // this produces an S-like curve + + path = new g.Path(); + + var segment; + + segment = g.Path.createSegment('M', sourcePoint); + path.appendSegment(segment); + + if ((Math.abs(sourcePoint.x - targetPoint.x)) >= (Math.abs(sourcePoint.y - targetPoint.y))) { + var controlPointX = (sourcePoint.x + targetPoint.x) / 2; + + segment = g.Path.createSegment('C', controlPointX, sourcePoint.y, controlPointX, targetPoint.y, targetPoint.x, targetPoint.y); + path.appendSegment(segment); + + } else { + var controlPointY = (sourcePoint.y + targetPoint.y) / 2; + + segment = g.Path.createSegment('C', sourcePoint.x, controlPointY, targetPoint.x, controlPointY, targetPoint.x, targetPoint.y); + path.appendSegment(segment); + + } + } + + return (raw) ? path : path.serialize(); +}; + +joint.connectors.jumpover = (function(_, g, util) { + + // default size of jump if not specified in options + var JUMP_SIZE = 5; + + // available jump types + // first one taken as default + var JUMP_TYPES = ['arc', 'gap', 'cubic']; + + // takes care of math. error for case when jump is too close to end of line + var CLOSE_PROXIMITY_PADDING = 1; + + // list of connector types not to jump over. + var IGNORED_CONNECTORS = ['smooth']; + + /** + * Transform start/end and route into series of lines + * @param {g.point} sourcePoint start point + * @param {g.point} targetPoint end point + * @param {g.point[]} route optional list of route + * @return {g.line[]} [description] + */ + function createLines(sourcePoint, targetPoint, route) { + // make a flattened array of all points + var points = [].concat(sourcePoint, route, targetPoint); + return points.reduce(function(resultLines, point, idx) { + // if there is a next point, make a line with it + var nextPoint = points[idx + 1]; + if (nextPoint != null) { + resultLines[idx] = g.line(point, nextPoint); + } + return resultLines; + }, []); + } + + function setupUpdating(jumpOverLinkView) { + var updateList = jumpOverLinkView.paper._jumpOverUpdateList; + + // first time setup for this paper + if (updateList == null) { + updateList = jumpOverLinkView.paper._jumpOverUpdateList = []; + jumpOverLinkView.paper.on('cell:pointerup', updateJumpOver); + jumpOverLinkView.paper.model.on('reset', function() { + updateList = jumpOverLinkView.paper._jumpOverUpdateList = []; + }); + } + + // add this link to a list so it can be updated when some other link is updated + if (updateList.indexOf(jumpOverLinkView) < 0) { + updateList.push(jumpOverLinkView); + + // watch for change of connector type or removal of link itself + // to remove the link from a list of jump over connectors + jumpOverLinkView.listenToOnce(jumpOverLinkView.model, 'change:connector remove', function() { + updateList.splice(updateList.indexOf(jumpOverLinkView), 1); + }); + } + } + + /** + * Handler for a batch:stop event to force + * update of all registered links with jump over connector + * @param {object} batchEvent optional object with info about batch + */ + function updateJumpOver() { + var updateList = this._jumpOverUpdateList; + for (var i = 0; i < updateList.length; i++) { + updateList[i].update(); + } + } + + /** + * Utility function to collect all intersection poinst of a single + * line against group of other lines. + * @param {g.line} line where to find points + * @param {g.line[]} crossCheckLines lines to cross + * @return {g.point[]} list of intersection points + */ + function findLineIntersections(line, crossCheckLines) { + return util.toArray(crossCheckLines).reduce(function(res, crossCheckLine) { + var intersection = line.intersection(crossCheckLine); + if (intersection) { + res.push(intersection); + } + return res; + }, []); + } + + /** + * Sorting function for list of points by their distance. + * @param {g.point} p1 first point + * @param {g.point} p2 second point + * @return {number} squared distance between points + */ + function sortPoints(p1, p2) { + return g.line(p1, p2).squaredLength(); + } + + /** + * Split input line into multiple based on intersection points. + * @param {g.line} line input line to split + * @param {g.point[]} intersections poinst where to split the line + * @param {number} jumpSize the size of jump arc (length empty spot on a line) + * @return {g.line[]} list of lines being split + */ + function createJumps(line, intersections, jumpSize) { + return intersections.reduce(function(resultLines, point, idx) { + // skipping points that were merged with the previous line + // to make bigger arc over multiple lines that are close to each other + if (point.skip === true) { + return resultLines; + } + + // always grab the last line from buffer and modify it + var lastLine = resultLines.pop() || line; + + // calculate start and end of jump by moving by a given size of jump + var jumpStart = g.point(point).move(lastLine.start, -(jumpSize)); + var jumpEnd = g.point(point).move(lastLine.start, +(jumpSize)); + + // now try to look at the next intersection point + var nextPoint = intersections[idx + 1]; + if (nextPoint != null) { + var distance = jumpEnd.distance(nextPoint); + if (distance <= jumpSize) { + // next point is close enough, move the jump end by this + // difference and mark the next point to be skipped + jumpEnd = nextPoint.move(lastLine.start, distance); + nextPoint.skip = true; + } + } else { + // this block is inside of `else` as an optimization so the distance is + // not calculated when we know there are no other intersection points + var endDistance = jumpStart.distance(lastLine.end); + // if the end is too close to possible jump, draw remaining line instead of a jump + if (endDistance < jumpSize * 2 + CLOSE_PROXIMITY_PADDING) { + resultLines.push(lastLine); + return resultLines; + } + } + + var startDistance = jumpEnd.distance(lastLine.start); + if (startDistance < jumpSize * 2 + CLOSE_PROXIMITY_PADDING) { + // if the start of line is too close to jump, draw that line instead of a jump + resultLines.push(lastLine); + return resultLines; + } + + // finally create a jump line + var jumpLine = g.line(jumpStart, jumpEnd); + // it's just simple line but with a `isJump` property + jumpLine.isJump = true; + + resultLines.push( + g.line(lastLine.start, jumpStart), + jumpLine, + g.line(jumpEnd, lastLine.end) + ); + return resultLines; + }, []); + } + + /** + * Assemble `D` attribute of a SVG path by iterating given lines. + * @param {g.line[]} lines source lines to use + * @param {number} jumpSize the size of jump arc (length empty spot on a line) + * @return {string} + */ + function buildPath(lines, jumpSize, jumpType) { + + var path = new g.Path(); + var segment; + + // first move to the start of a first line + segment = g.Path.createSegment('M', lines[0].start); + path.appendSegment(segment); + + // make a paths from lines + joint.util.toArray(lines).forEach(function(line, index) { + + if (line.isJump) { + var angle, diff; + + var control1, control2; + + if (jumpType === 'arc') { // approximates semicircle with 2 curves + angle = -90; + // determine rotation of arc based on difference between points + diff = line.start.difference(line.end); + // make sure the arc always points up (or right) + var xAxisRotate = Number((diff.x < 0) || (diff.x === 0 && diff.y < 0)); + if (xAxisRotate) angle += 180; + + var midpoint = line.midpoint(); + var centerLine = new g.Line(midpoint, line.end).rotate(midpoint, angle); + + var halfLine; + + // first half + halfLine = new g.Line(line.start, midpoint); + + control1 = halfLine.pointAt(2 / 3).rotate(line.start, angle); + control2 = centerLine.pointAt(1 / 3).rotate(centerLine.end, -angle); + + segment = g.Path.createSegment('C', control1, control2, centerLine.end); + path.appendSegment(segment); + + // second half + halfLine = new g.Line(midpoint, line.end); + + control1 = centerLine.pointAt(1 / 3).rotate(centerLine.end, angle); + control2 = halfLine.pointAt(1 / 3).rotate(line.end, -angle); + + segment = g.Path.createSegment('C', control1, control2, line.end); + path.appendSegment(segment); + + } else if (jumpType === 'gap') { + segment = g.Path.createSegment('M', line.end); + path.appendSegment(segment); + + } else if (jumpType === 'cubic') { // approximates semicircle with 1 curve + angle = line.start.theta(line.end); + + var xOffset = jumpSize * 0.6; + var yOffset = jumpSize * 1.35; + + // determine rotation of arc based on difference between points + diff = line.start.difference(line.end); + // make sure the arc always points up (or right) + xAxisRotate = Number((diff.x < 0) || (diff.x === 0 && diff.y < 0)); + if (xAxisRotate) yOffset *= -1; + + control1 = g.Point(line.start.x + xOffset, line.start.y + yOffset).rotate(line.start, angle); + control2 = g.Point(line.end.x - xOffset, line.end.y + yOffset).rotate(line.end, angle); + + segment = g.Path.createSegment('C', control1, control2, line.end); + path.appendSegment(segment); + } + + } else { + segment = g.Path.createSegment('L', line.end); + path.appendSegment(segment); + } + }); + + return path; + } + + /** + * Actual connector function that will be run on every update. + * @param {g.point} sourcePoint start point of this link + * @param {g.point} targetPoint end point of this link + * @param {g.point[]} route of this link + * @param {object} opt options + * @property {number} size optional size of a jump arc + * @return {string} created `D` attribute of SVG path + */ + return function(sourcePoint, targetPoint, route, opt) { // eslint-disable-line max-params + + setupUpdating(this); + + var raw = opt.raw; + var jumpSize = opt.size || JUMP_SIZE; + var jumpType = opt.jump && ('' + opt.jump).toLowerCase(); + var ignoreConnectors = opt.ignoreConnectors || IGNORED_CONNECTORS; + + // grab the first jump type as a default if specified one is invalid + if (JUMP_TYPES.indexOf(jumpType) === -1) { + jumpType = JUMP_TYPES[0]; + } + + var paper = this.paper; + var graph = paper.model; + var allLinks = graph.getLinks(); + + // there is just one link, draw it directly + if (allLinks.length === 1) { + return buildPath( + createLines(sourcePoint, targetPoint, route), + jumpSize, jumpType + ); + } + + var thisModel = this.model; + var thisIndex = allLinks.indexOf(thisModel); + var defaultConnector = paper.options.defaultConnector || {}; + + // not all links are meant to be jumped over. + var links = allLinks.filter(function(link, idx) { + + var connector = link.get('connector') || defaultConnector; + + // avoid jumping over links with connector type listed in `ignored connectors`. + if (util.toArray(ignoreConnectors).includes(connector.name)) { + return false; + } + // filter out links that are above this one and have the same connector type + // otherwise there would double hoops for each intersection + if (idx > thisIndex) { + return connector.name !== 'jumpover'; + } + return true; + }); + + // find views for all links + var linkViews = links.map(function(link) { + return paper.findViewByModel(link); + }); + + // create lines for this link + var thisLines = createLines( + sourcePoint, + targetPoint, + route + ); + + // create lines for all other links + var linkLines = linkViews.map(function(linkView) { + if (linkView == null) { + return []; + } + if (linkView === this) { + return thisLines; + } + return createLines( + linkView.sourcePoint, + linkView.targetPoint, + linkView.route + ); + }, this); + + // transform lines for this link by splitting with jump lines at + // points of intersection with other links + var jumpingLines = thisLines.reduce(function(resultLines, thisLine) { + // iterate all links and grab the intersections with this line + // these are then sorted by distance so the line can be split more easily + + var intersections = links.reduce(function(res, link, i) { + // don't intersection with itself + if (link !== thisModel) { + + var lineIntersections = findLineIntersections(thisLine, linkLines[i]); + res.push.apply(res, lineIntersections); + } + return res; + }, []).sort(function(a, b) { + return sortPoints(thisLine.start, a) - sortPoints(thisLine.start, b); + }); + + if (intersections.length > 0) { + // split the line based on found intersection points + resultLines.push.apply(resultLines, createJumps(thisLine, intersections, jumpSize)); + } else { + // without any intersection the line goes uninterrupted + resultLines.push(thisLine); + } + return resultLines; + }, []); + + var path = buildPath(jumpingLines, jumpSize, jumpType); + return (raw) ? path : path.serialize(); + }; +}(_, g, joint.util)); + +(function(_, g, joint, util) { + + function portTransformAttrs(point, angle, opt) { + + var trans = point.toJSON(); + + trans.angle = angle || 0; + + return joint.util.defaults({}, opt, trans); + } + + function lineLayout(ports, p1, p2) { + return ports.map(function(port, index, ports) { + var p = this.pointAt(((index + 0.5) / ports.length)); + // `dx`,`dy` per port offset option + if (port.dx || port.dy) { + p.offset(port.dx || 0, port.dy || 0); + } + + return portTransformAttrs(p.round(), 0, port); + }, g.line(p1, p2)); + } + + function ellipseLayout(ports, elBBox, startAngle, stepFn) { + + var center = elBBox.center(); + var ratio = elBBox.width / elBBox.height; + var p1 = elBBox.topMiddle(); + + var ellipse = g.Ellipse.fromRect(elBBox); + + return ports.map(function(port, index, ports) { + + var angle = startAngle + stepFn(index, ports.length); + var p2 = p1.clone() + .rotate(center, -angle) + .scale(ratio, 1, center); + + var theta = port.compensateRotation ? -ellipse.tangentTheta(p2) : 0; + + // `dx`,`dy` per port offset option + if (port.dx || port.dy) { + p2.offset(port.dx || 0, port.dy || 0); + } + + // `dr` delta radius option + if (port.dr) { + p2.move(center, port.dr); + } + + return portTransformAttrs(p2.round(), theta, port); + }); + } + + // Creates a point stored in arguments + function argPoint(bbox, args) { + + var x = args.x; + if (util.isString(x)) { + x = parseFloat(x) / 100 * bbox.width; + } + + var y = args.y; + if (util.isString(y)) { + y = parseFloat(y) / 100 * bbox.height; + } + + return g.point(x || 0, y || 0); + } + + joint.layout.Port = { + + /** + * @param {Array} ports + * @param {g.Rect} elBBox + * @param {Object=} opt opt Group options + * @returns {Array} + */ + absolute: function(ports, elBBox, opt) { + //TODO v.talas angle + return ports.map(argPoint.bind(null, elBBox)); + }, + + /** + * @param {Array} ports + * @param {g.Rect} elBBox + * @param {Object=} opt opt Group options + * @returns {Array} + */ + fn: function(ports, elBBox, opt) { + return opt.fn(ports, elBBox, opt); + }, + + /** + * @param {Array} ports + * @param {g.Rect} elBBox + * @param {Object=} opt opt Group options + * @returns {Array} + */ + line: function(ports, elBBox, opt) { + + var start = argPoint(elBBox, opt.start || elBBox.origin()); + var end = argPoint(elBBox, opt.end || elBBox.corner()); + + return lineLayout(ports, start, end); + }, + + /** + * @param {Array} ports + * @param {g.Rect} elBBox + * @param {Object=} opt opt Group options + * @returns {Array} + */ + left: function(ports, elBBox, opt) { + return lineLayout(ports, elBBox.origin(), elBBox.bottomLeft()); + }, + + /** + * @param {Array} ports + * @param {g.Rect} elBBox + * @param {Object=} opt opt Group options + * @returns {Array} + */ + right: function(ports, elBBox, opt) { + return lineLayout(ports, elBBox.topRight(), elBBox.corner()); + }, + + /** + * @param {Array} ports + * @param {g.Rect} elBBox + * @param {Object=} opt opt Group options + * @returns {Array} + */ + top: function(ports, elBBox, opt) { + return lineLayout(ports, elBBox.origin(), elBBox.topRight()); + }, + + /** + * @param {Array} ports + * @param {g.Rect} elBBox + * @param {Object=} opt opt Group options + * @returns {Array} + */ + bottom: function(ports, elBBox, opt) { + return lineLayout(ports, elBBox.bottomLeft(), elBBox.corner()); + }, + + /** + * @param {Array} ports + * @param {g.Rect} elBBox + * @param {Object=} opt Group options + * @returns {Array} + */ + ellipseSpread: function(ports, elBBox, opt) { + + var startAngle = opt.startAngle || 0; + var stepAngle = opt.step || 360 / ports.length; + + return ellipseLayout(ports, elBBox, startAngle, function(index) { + return index * stepAngle; + }); + }, + + /** + * @param {Array} ports + * @param {g.Rect} elBBox + * @param {Object=} opt Group options + * @returns {Array} + */ + ellipse: function(ports, elBBox, opt) { + + var startAngle = opt.startAngle || 0; + var stepAngle = opt.step || 20; + + return ellipseLayout(ports, elBBox, startAngle, function(index, count) { + return (index + 0.5 - count / 2) * stepAngle; + }); + } + }; + +})(_, g, joint, joint.util); + +(function(_, g, joint, util) { + + function labelAttributes(opt1, opt2) { + + return util.defaultsDeep({}, opt1, opt2, { + x: 0, + y: 0, + angle: 0, + attrs: { + '.': { + y: '0', + 'text-anchor': 'start' + } + } + }); + + } + + function outsideLayout(portPosition, elBBox, autoOrient, opt) { + + opt = util.defaults({}, opt, { offset: 15 }); + var angle = elBBox.center().theta(portPosition); + var x = getBBoxAngles(elBBox); + + var tx, ty, y, textAnchor; + var offset = opt.offset; + var orientAngle = 0; + + if (angle < x[1] || angle > x[2]) { + y = '.3em'; + tx = offset; + ty = 0; + textAnchor = 'start'; + } else if (angle < x[0]) { + y = '0'; + tx = 0; + ty = -offset; + if (autoOrient) { + orientAngle = -90; + textAnchor = 'start'; + } else { + textAnchor = 'middle'; + } + } else if (angle < x[3]) { + y = '.3em'; + tx = -offset; + ty = 0; + textAnchor = 'end'; + } else { + y = '.6em'; + tx = 0; + ty = offset; + if (autoOrient) { + orientAngle = 90; + textAnchor = 'start'; + } else { + textAnchor = 'middle'; + } + } + + var round = Math.round; + return labelAttributes({ + x: round(tx), + y: round(ty), + angle: orientAngle, + attrs: { + '.': { + y: y, + 'text-anchor': textAnchor + } + } + }); + } + + function getBBoxAngles(elBBox) { + + var center = elBBox.center(); + + var tl = center.theta(elBBox.origin()); + var bl = center.theta(elBBox.bottomLeft()); + var br = center.theta(elBBox.corner()); + var tr = center.theta(elBBox.topRight()); + + return [tl, tr, br, bl]; + } + + function insideLayout(portPosition, elBBox, autoOrient, opt) { + + var angle = elBBox.center().theta(portPosition); + opt = util.defaults({}, opt, { offset: 15 }); + + var tx, ty, y, textAnchor; + var offset = opt.offset; + var orientAngle = 0; + + var bBoxAngles = getBBoxAngles(elBBox); + + if (angle < bBoxAngles[1] || angle > bBoxAngles[2]) { + y = '.3em'; + tx = -offset; + ty = 0; + textAnchor = 'end'; + } else if (angle < bBoxAngles[0]) { + y = '.6em'; + tx = 0; + ty = offset; + if (autoOrient) { + orientAngle = 90; + textAnchor = 'start'; + } else { + textAnchor = 'middle'; + } + } else if (angle < bBoxAngles[3]) { + y = '.3em'; + tx = offset; + ty = 0; + textAnchor = 'start'; + } else { + y = '0em'; + tx = 0; + ty = -offset; + if (autoOrient) { + orientAngle = -90; + textAnchor = 'start'; + } else { + textAnchor = 'middle'; + } + } + + var round = Math.round; + return labelAttributes({ + x: round(tx), + y: round(ty), + angle: orientAngle, + attrs: { + '.': { + y: y, + 'text-anchor': textAnchor + } + } + }); + } + + function radialLayout(portCenterOffset, autoOrient, opt) { + + opt = util.defaults({}, opt, { offset: 20 }); + + var origin = g.point(0, 0); + var angle = -portCenterOffset.theta(origin); + var orientAngle = angle; + var offset = portCenterOffset.clone() + .move(origin, opt.offset) + .difference(portCenterOffset) + .round(); + + var y = '.3em'; + var textAnchor; + + if ((angle + 90) % 180 === 0) { + textAnchor = autoOrient ? 'end' : 'middle'; + if (!autoOrient && angle === -270) { + y = '0em'; + } + } else if (angle > -270 && angle < -90) { + textAnchor = 'start'; + orientAngle = angle - 180; + } else { + textAnchor = 'end'; + } + + var round = Math.round; + return labelAttributes({ + x: round(offset.x), + y: round(offset.y), + angle: autoOrient ? orientAngle : 0, + attrs: { + '.': { + y: y, + 'text-anchor': textAnchor + } + } + }); + } + + joint.layout.PortLabel = { + + manual: function(portPosition, elBBox, opt) { + return labelAttributes(opt, elBBox); + }, + + left: function(portPosition, elBBox, opt) { + return labelAttributes(opt, { x: -15, attrs: { '.': { y: '.3em', 'text-anchor': 'end' } } }); + }, + + right: function(portPosition, elBBox, opt) { + return labelAttributes(opt, { x: 15, attrs: { '.': { y: '.3em', 'text-anchor': 'start' } } }); + }, + + top: function(portPosition, elBBox, opt) { + return labelAttributes(opt, { y: -15, attrs: { '.': { 'text-anchor': 'middle' } } }); + }, + + bottom: function(portPosition, elBBox, opt) { + return labelAttributes(opt, { y: 15, attrs: { '.': { y: '.6em', 'text-anchor': 'middle' } } }); + }, + + outsideOriented: function(portPosition, elBBox, opt) { + return outsideLayout(portPosition, elBBox, true, opt); + }, + + outside: function(portPosition, elBBox, opt) { + return outsideLayout(portPosition, elBBox, false, opt); + }, + + insideOriented: function(portPosition, elBBox, opt) { + return insideLayout(portPosition, elBBox, true, opt); + }, + + inside: function(portPosition, elBBox, opt) { + return insideLayout(portPosition, elBBox, false, opt); + }, + + radial: function(portPosition, elBBox, opt) { + return radialLayout(portPosition.difference(elBBox.center()), false, opt); + }, + + radialOriented: function(portPosition, elBBox, opt) { + return radialLayout(portPosition.difference(elBBox.center()), true, opt); + } + }; + +})(_, g, joint, joint.util); + +joint.highlighters.addClass = { + + className: joint.util.addClassNamePrefix('highlighted'), + + /** + * @param {joint.dia.CellView} cellView + * @param {Element} magnetEl + * @param {object=} opt + */ + highlight: function(cellView, magnetEl, opt) { + + var options = opt || {}; + var className = options.className || this.className; + V(magnetEl).addClass(className); + }, + + /** + * @param {joint.dia.CellView} cellView + * @param {Element} magnetEl + * @param {object=} opt + */ + unhighlight: function(cellView, magnetEl, opt) { + + var options = opt || {}; + var className = options.className || this.className; + V(magnetEl).removeClass(className); + } +}; + +joint.highlighters.opacity = { + + /** + * @param {joint.dia.CellView} cellView + * @param {Element} magnetEl + */ + highlight: function(cellView, magnetEl) { + + V(magnetEl).addClass(joint.util.addClassNamePrefix('highlight-opacity')); + }, + + /** + * @param {joint.dia.CellView} cellView + * @param {Element} magnetEl + */ + unhighlight: function(cellView, magnetEl) { + + V(magnetEl).removeClass(joint.util.addClassNamePrefix('highlight-opacity')); + } +}; + +joint.highlighters.stroke = { + + defaultOptions: { + padding: 3, + rx: 0, + ry: 0, + attrs: { + 'stroke-width': 3, + stroke: '#FEB663' + } + }, + + _views: {}, + + getHighlighterId: function(magnetEl, opt) { + + return magnetEl.id + JSON.stringify(opt); + }, + + removeHighlighter: function(id) { + if (this._views[id]) { + this._views[id].remove(); + this._views[id] = null; + } + }, + + /** + * @param {joint.dia.CellView} cellView + * @param {Element} magnetEl + * @param {object=} opt + */ + highlight: function(cellView, magnetEl, opt) { + + var id = this.getHighlighterId(magnetEl, opt); + + // Only highlight once. + if (this._views[id]) return; + + var options = joint.util.defaults(opt || {}, this.defaultOptions); + + var magnetVel = V(magnetEl); + var magnetBBox; + + try { + + var pathData = magnetVel.convertToPathData(); + + } catch (error) { + + // Failed to get path data from magnet element. + // Draw a rectangle around the entire cell view instead. + magnetBBox = magnetVel.bbox(true/* without transforms */); + pathData = V.rectToPath(joint.util.assign({}, options, magnetBBox)); + } + + var highlightVel = V('path').attr({ + d: pathData, + 'pointer-events': 'none', + 'vector-effect': 'non-scaling-stroke', + 'fill': 'none' + }).attr(options.attrs); + + var highlightMatrix = magnetVel.getTransformToElement(cellView.el); + + // Add padding to the highlight element. + var padding = options.padding; + if (padding) { + + magnetBBox || (magnetBBox = magnetVel.bbox(true)); + + var cx = magnetBBox.x + (magnetBBox.width / 2); + var cy = magnetBBox.y + (magnetBBox.height / 2); + + magnetBBox = V.transformRect(magnetBBox, highlightMatrix); + + var width = Math.max(magnetBBox.width, 1); + var height = Math.max(magnetBBox.height, 1); + var sx = (width + padding) / width; + var sy = (height + padding) / height; + + var paddingMatrix = V.createSVGMatrix({ + a: sx, + b: 0, + c: 0, + d: sy, + e: cx - sx * cx, + f: cy - sy * cy + }); + + highlightMatrix = highlightMatrix.multiply(paddingMatrix); + } + + highlightVel.transform(highlightMatrix); + + // joint.mvc.View will handle the theme class name and joint class name prefix. + var highlightView = this._views[id] = new joint.mvc.View({ + svgElement: true, + className: 'highlight-stroke', + el: highlightVel.node + }); + + // Remove the highlight view when the cell is removed from the graph. + var removeHandler = this.removeHighlighter.bind(this, id); + var cell = cellView.model; + highlightView.listenTo(cell, 'remove', removeHandler); + highlightView.listenTo(cell.graph, 'reset', removeHandler); + + cellView.vel.append(highlightVel); + }, + + /** + * @param {joint.dia.CellView} cellView + * @param {Element} magnetEl + * @param {object=} opt + */ + unhighlight: function(cellView, magnetEl, opt) { + + this.removeHighlighter(this.getHighlighterId(magnetEl, opt)); + } +}; + +(function(joint, util) { + + function bboxWrapper(method) { + + return function(view, magnet, ref, opt) { + + var rotate = !!opt.rotate; + var bbox = (rotate) ? view.getNodeUnrotatedBBox(magnet) : view.getNodeBBox(magnet); + var anchor = bbox[method](); + + var dx = opt.dx; + if (dx) { + var dxPercentage = util.isPercentage(dx); + dx = parseFloat(dx); + if (isFinite(dx)) { + if (dxPercentage) { + dx /= 100; + dx *= bbox.width; + } + anchor.x += dx; + } + } + + var dy = opt.dy; + if (dy) { + var dyPercentage = util.isPercentage(dy); + dy = parseFloat(dy); + if (isFinite(dy)) { + if (dyPercentage) { + dy /= 100; + dy *= bbox.height; + } + anchor.y += dy; + } + } + + return (rotate) ? anchor.rotate(view.model.getBBox().center(), -view.model.angle()) : anchor; + } + } + + function resolveRefAsBBoxCenter(fn) { + + return function(view, magnet, ref, opt) { + + if (ref instanceof Element) { + var refView = this.paper.findView(ref); + var refPoint = (refView) + ? refView.getNodeBBox(ref).center() + : new g.Point() + + return fn.call(this, view, magnet, refPoint, opt) + } + + return fn.apply(this, arguments); + } + } + + function perpendicular(view, magnet, refPoint, opt) { + + var angle = view.model.angle(); + var bbox = view.getNodeBBox(magnet); + var anchor = bbox.center(); + var topLeft = bbox.origin(); + var bottomRight = bbox.corner(); + + var padding = opt.padding + if (!isFinite(padding)) padding = 0; + + if ((topLeft.y + padding) <= refPoint.y && refPoint.y <= (bottomRight.y - padding)) { + var dy = (refPoint.y - anchor.y); + anchor.x += (angle === 0 || angle === 180) ? 0 : dy * 1 / Math.tan(g.toRad(angle)); + anchor.y += dy; + } else if ((topLeft.x + padding) <= refPoint.x && refPoint.x <= (bottomRight.x - padding)) { + var dx = (refPoint.x - anchor.x); + anchor.y += (angle === 90 || angle === 270) ? 0 : dx * Math.tan(g.toRad(angle)); + anchor.x += dx; + } + + return anchor; + } + + function midSide(view, magnet, refPoint, opt) { + + var rotate = !!opt.rotate; + var bbox, angle, center; + if (rotate) { + bbox = view.getNodeUnrotatedBBox(magnet); + center = view.model.getBBox().center(); + angle = view.model.angle(); + } else { + bbox = view.getNodeBBox(magnet); + } + + var padding = opt.padding; + if (isFinite(padding)) bbox.inflate(padding); + + if (rotate) refPoint.rotate(center, angle); + + var side = bbox.sideNearestToPoint(refPoint); + var anchor; + switch (side) { + case 'left': anchor = bbox.leftMiddle(); break; + case 'right': anchor = bbox.rightMiddle(); break; + case 'top': anchor = bbox.topMiddle(); break; + case 'bottom': anchor = bbox.bottomMiddle(); break; + } + + return (rotate) ? anchor.rotate(center, -angle) : anchor; + } + + // Can find anchor from model, when there is no selector or the link end + // is connected to a port + function modelCenter(view, magnet) { + + var model = view.model; + var bbox = model.getBBox(); + var center = bbox.center(); + var angle = model.angle(); + + var portId = view.findAttribute('port', magnet); + if (portId) { + portGroup = model.portProp(portId, 'group'); + var portsPositions = model.getPortsPositions(portGroup); + var anchor = new g.Point(portsPositions[portId]).offset(bbox.origin()); + anchor.rotate(center, -angle); + return anchor; + } + + return center; + } + + joint.anchors = { + center: bboxWrapper('center'), + top: bboxWrapper('topMiddle'), + bottom: bboxWrapper('bottomMiddle'), + left: bboxWrapper('leftMiddle'), + right: bboxWrapper('rightMiddle'), + topLeft: bboxWrapper('origin'), + topRight: bboxWrapper('topRight'), + bottomLeft: bboxWrapper('bottomLeft'), + bottomRight: bboxWrapper('corner'), + perpendicular: resolveRefAsBBoxCenter(perpendicular), + midSide: resolveRefAsBBoxCenter(midSide), + modelCenter: modelCenter + } + +})(joint, joint.util); + +(function(joint, util, g, V) { + + function closestIntersection(intersections, refPoint) { + + if (intersections.length === 1) return intersections[0]; + return util.sortBy(intersections, function(i) { return i.squaredDistance(refPoint) })[0]; + } + + function offset(p1, p2, offset) { + + if (!isFinite(offset)) return p1; + var length = p1.distance(p2); + if (offset === 0 && length > 0) return p1; + return p1.move(p2, -Math.min(offset, length - 1)); + } + + function stroke(magnet) { + + var stroke = magnet.getAttribute('stroke-width'); + if (stroke === null) return 0; + return parseFloat(stroke) || 0; + } + + // Connection Points + + function anchor(line, view, magnet, opt) { + + return offset(line.end, line.start, opt.offset); + } + + function bboxIntersection(line, view, magnet, opt) { + + var bbox = view.getNodeBBox(magnet); + if (opt.stroke) bbox.inflate(stroke(magnet) / 2); + var intersections = line.intersect(bbox); + var cp = (intersections) + ? closestIntersection(intersections, line.start) + : line.end; + return offset(cp, line.start, opt.offset); + } + + function rectangleIntersection(line, view, magnet, opt) { + + var angle = view.model.angle(); + if (angle === 0) { + return bboxIntersection(line, view, magnet, opt); + } + + var bboxWORotation = view.getNodeUnrotatedBBox(magnet); + if (opt.stroke) bboxWORotation.inflate(stroke(magnet) / 2); + var center = bboxWORotation.center(); + var lineWORotation = line.clone().rotate(center, angle); + var intersections = lineWORotation.setLength(1e6).intersect(bboxWORotation); + var cp = (intersections) + ? closestIntersection(intersections, lineWORotation.start).rotate(center, -angle) + : line.end; + return offset(cp, line.start, opt.offset); + } + + var BNDR_SUBDIVISIONS = 'segmentSubdivisons'; + var BNDR_SHAPE_BBOX = 'shapeBBox'; + + function boundaryIntersection(line, view, magnet, opt) { + + var node, intersection; + var selector = opt.selector; + var anchor = line.end; + + if (typeof selector === 'string') { + node = view.findBySelector(selector)[0]; + } else if (Array.isArray(selector)) { + node = util.getByPath(magnet, selector); + } else { + // Find the closest non-group descendant + node = magnet; + do { + var tagName = node.tagName.toUpperCase(); + if (tagName === 'G') { + node = node.firstChild; + } else if (tagName === 'TITLE') { + node = node.nextSibling; + } else break; + } while (node) + } + + if (!(node instanceof Element)) return anchor; + + var localShape = view.getNodeShape(node); + var magnetMatrix = view.getNodeMatrix(node); + var translateMatrix = view.getRootTranslateMatrix(); + var rotateMatrix = view.getRootRotateMatrix(); + var targetMatrix = translateMatrix.multiply(rotateMatrix).multiply(magnetMatrix); + var localMatrix = targetMatrix.inverse(); + var localLine = V.transformLine(line, localMatrix); + var localRef = localLine.start.clone(); + var data = view.getNodeData(node); + + if (opt.insideout === false) { + if (!data[BNDR_SHAPE_BBOX]) data[BNDR_SHAPE_BBOX] = localShape.bbox(); + var localBBox = data[BNDR_SHAPE_BBOX]; + if (localBBox.containsPoint(localRef)) return anchor; + } + + // Caching segment subdivisions for paths + var pathOpt + if (localShape instanceof g.Path) { + var precision = opt.precision || 2; + if (!data[BNDR_SUBDIVISIONS]) data[BNDR_SUBDIVISIONS] = localShape.getSegmentSubdivisions({ precision: precision }); + segmentSubdivisions = data[BNDR_SUBDIVISIONS]; + pathOpt = { + precision: precision, + segmentSubdivisions: data[BNDR_SUBDIVISIONS] + } + } + + if (opt.extrapolate === true) localLine.setLength(1e6); + + intersection = localLine.intersect(localShape, pathOpt); + if (intersection) { + // More than one intersection + if (V.isArray(intersection)) intersection = closestIntersection(intersection, localRef); + } else if (opt.sticky === true) { + // No intersection, find the closest point instead + if (localShape instanceof g.Rect) { + intersection = localShape.pointNearestToPoint(localRef); + } else if (localShape instanceof g.Ellipse) { + intersection = localShape.intersectionWithLineFromCenterToPoint(localRef); + } else { + intersection = localShape.closestPoint(localRef, pathOpt); + } + } + + var cp = (intersection) ? V.transformPoint(intersection, targetMatrix) : anchor; + var cpOffset = opt.offset || 0; + if (opt.stroke) cpOffset += stroke(node) / 2; + + return offset(cp, line.start, cpOffset); + } + + joint.connectionPoints = { + anchor: anchor, + bbox: bboxIntersection, + rectangle: rectangleIntersection, + boundary: boundaryIntersection + } + +})(joint, joint.util, g, V); + +(function(joint, util) { + + function abs2rel(value, max) { + + if (max === 0) return '0%'; + return Math.round(value / max * 100) + '%'; + } + + function pin(relative) { + + return function(end, view, magnet, coords) { + + var angle = view.model.angle(); + var bbox = view.getNodeUnrotatedBBox(magnet); + var origin = view.model.getBBox().center(); + coords.rotate(origin, angle); + var dx = coords.x - bbox.x; + var dy = coords.y - bbox.y; + + if (relative) { + dx = abs2rel(dx, bbox.width); + dy = abs2rel(dy, bbox.height); + } + + end.anchor = { + name: 'topLeft', + args: { + dx: dx, + dy: dy, + rotate: true + } + }; + + return end; + } + } + + joint.connectionStrategies = { + useDefaults: util.noop, + pinAbsolute: pin(false), + pinRelative: pin(true) + } + +})(joint, joint.util); + +(function(joint, util, V, g) { + + function getAnchor(coords, view, magnet) { + // take advantage of an existing logic inside of the + // pin relative connection strategy + var end = joint.connectionStrategies.pinRelative.call( + this.paper, + {}, + view, + magnet, + coords, + this.model + ); + return end.anchor; + } + + function snapAnchor(coords, view, magnet, type, relatedView, toolView) { + var snapRadius = toolView.options.snapRadius; + var isSource = (type === 'source'); + var refIndex = (isSource ? 0 : -1); + var ref = this.model.vertex(refIndex) || this.getEndAnchor(isSource ? 'target' : 'source'); + if (ref) { + if (Math.abs(ref.x - coords.x) < snapRadius) coords.x = ref.x; + if (Math.abs(ref.y - coords.y) < snapRadius) coords.y = ref.y; + } + return coords; + } + + var ToolView = joint.dia.ToolView; + + // Vertex Handles + var VertexHandle = joint.mvc.View.extend({ + tagName: 'circle', + svgElement: true, + className: 'marker-vertex', + events: { + mousedown: 'onPointerDown', + touchstart: 'onPointerDown', + dblclick: 'onDoubleClick' + }, + documentEvents: { + mousemove: 'onPointerMove', + touchmove: 'onPointerMove', + mouseup: 'onPointerUp', + touchend: 'onPointerUp' + }, + attributes: { + 'r': 6, + 'fill': '#33334F', + 'stroke': '#FFFFFF', + 'stroke-width': 2, + 'cursor': 'move' + }, + position: function(x, y) { + this.vel.attr({ cx: x, cy: y }); + }, + onPointerDown: function(evt) { + evt.stopPropagation(); + this.options.paper.undelegateEvents(); + this.delegateDocumentEvents(null, evt.data); + this.trigger('will-change'); + }, + onPointerMove: function(evt) { + this.trigger('changing', this, evt); + }, + onDoubleClick: function(evt) { + this.trigger('remove', this, evt); + }, + onPointerUp: function(evt) { + this.trigger('changed', this, evt); + this.undelegateDocumentEvents(); + this.options.paper.delegateEvents(); + } + }); + + var Vertices = ToolView.extend({ + name: 'vertices', + options: { + handleClass: VertexHandle, + snapRadius: 20, + redundancyRemoval: true, + vertexAdding: true, + }, + children: [{ + tagName: 'path', + selector: 'connection', + className: 'joint-vertices-path', + attributes: { + 'fill': 'none', + 'stroke': 'transparent', + 'stroke-width': 10, + 'cursor': 'cell' + } + }], + handles: null, + events: { + 'mousedown .joint-vertices-path': 'onPathPointerDown' + }, + onRender: function() { + this.resetHandles(); + if (this.options.vertexAdding) { + this.renderChildren(); + this.updatePath(); + } + var relatedView = this.relatedView; + var vertices = relatedView.model.vertices(); + for (var i = 0, n = vertices.length; i < n; i++) { + var vertex = vertices[i]; + var handle = new (this.options.handleClass)({ index: i, paper: this.paper }); + handle.render(); + handle.position(vertex.x, vertex.y); + this.simulateRelatedView(handle.el); + handle.vel.appendTo(this.el); + this.handles.push(handle); + this.startHandleListening(handle); + } + return this; + }, + update: function() { + this.render(); + return this; + }, + updatePath: function() { + var connection = this.childNodes.connection; + if (connection) connection.setAttribute('d', this.relatedView.getConnection().serialize()); + }, + startHandleListening: function(handle) { + var relatedView = this.relatedView; + if (relatedView.can('vertexMove')) { + this.listenTo(handle, 'will-change', this.onHandleWillChange); + this.listenTo(handle, 'changing', this.onHandleChanging); + this.listenTo(handle, 'changed', this.onHandleChanged); + } + if (relatedView.can('vertexRemove')) { + this.listenTo(handle, 'remove', this.onHandleRemove); + } + }, + resetHandles: function() { + var handles = this.handles; + this.handles = []; + this.stopListening(); + if (!Array.isArray(handles)) return; + for (var i = 0, n = handles.length; i < n; i++) { + handles[i].remove(); + } + }, + getNeighborPoints: function(index) { + var linkView = this.relatedView; + var vertices = linkView.model.vertices(); + var prev = (index > 0) ? vertices[index - 1] : linkView.sourceAnchor; + var next = (index < vertices.length - 1) ? vertices[index + 1] : linkView.targetAnchor; + return { + prev: new g.Point(prev), + next: new g.Point(next) + } + }, + onHandleWillChange: function(handle, evt) { + this.focus(); + this.relatedView.model.startBatch('vertex-move', { ui: true, tool: this.cid }); + }, + onHandleChanging: function(handle, evt) { + var relatedView = this.relatedView; + var paper = relatedView.paper; + var index = handle.options.index; + var vertex = paper.snapToGrid(evt.clientX, evt.clientY).toJSON(); + this.snapVertex(vertex, index); + relatedView.model.vertex(index, vertex, { ui: true, tool: this.cid }); + handle.position(vertex.x, vertex.y); + }, + snapVertex: function(vertex, index) { + var snapRadius = this.options.snapRadius; + if (snapRadius > 0) { + var neighbors = this.getNeighborPoints(index); + var prev = neighbors.prev; + var next = neighbors.next; + if (Math.abs(vertex.x - prev.x) < snapRadius) { + vertex.x = prev.x; + } else if (Math.abs(vertex.x - next.x) < snapRadius) { + vertex.x = next.x; + } + if (Math.abs(vertex.y - prev.y) < snapRadius) { + vertex.y = neighbors.prev.y; + } else if (Math.abs(vertex.y - next.y) < snapRadius) { + vertex.y = next.y; + } + } + }, + onHandleChanged: function(handle, evt) { + if (this.options.vertexAdding) this.updatePath(); + if (!this.options.redundancyRemoval) return; + var linkView = this.relatedView; + var verticesRemoved = linkView.removeRedundantLinearVertices({ ui: true, tool: this.cid }); + if (verticesRemoved) this.render(); + this.blur(); + linkView.model.stopBatch('vertex-move', { ui: true, tool: this.cid }); + if (this.eventData(evt).vertexAdded) { + linkView.model.stopBatch('vertex-add', { ui: true, tool: this.cid }); + } + }, + onHandleRemove: function(handle) { + var index = handle.options.index; + this.relatedView.model.removeVertex(index, { ui: true }); + }, + onPathPointerDown: function(evt) { + evt.stopPropagation(); + var vertex = this.paper.snapToGrid(evt.clientX, evt.clientY).toJSON(); + var relatedView = this.relatedView; + relatedView.model.startBatch('vertex-add', { ui: true, tool: this.cid }); + var index = relatedView.getVertexIndex(vertex.x, vertex.y); + this.snapVertex(vertex, index); + relatedView.model.insertVertex(index, vertex, { ui: true, tool: this.cid }); + this.render(); + var handle = this.handles[index]; + this.eventData(evt, { vertexAdded: true }); + handle.onPointerDown(evt); + }, + onRemove: function() { + this.resetHandles(); + } + }, { + VertexHandle: VertexHandle // keep as class property + }); + + var SegmentHandle = joint.mvc.View.extend({ + tagName: 'g', + svgElement: true, + className: 'marker-segment', + events: { + mousedown: 'onPointerDown', + touchstart: 'onPointerDown' + }, + documentEvents: { + mousemove: 'onPointerMove', + touchmove: 'onPointerMove', + mouseup: 'onPointerUp', + touchend: 'onPointerUp' + }, + children: [{ + tagName: 'line', + selector: 'line', + attributes: { + 'stroke': '#33334F', + 'stroke-width': 2, + 'fill': 'none', + 'pointer-events': 'none' + } + }, { + tagName: 'rect', + selector: 'handle', + attributes: { + 'width': 20, + 'height': 8, + 'x': -10, + 'y': -4, + 'rx': 4, + 'ry': 4, + 'fill': '#33334F', + 'stroke': '#FFFFFF', + 'stroke-width': 2 + } + }], + onRender: function() { + this.renderChildren(); + }, + position: function(x, y, angle, view) { + + var matrix = V.createSVGMatrix().translate(x, y).rotate(angle); + var handle = this.childNodes.handle; + handle.setAttribute('transform', V.matrixToTransformString(matrix)); + handle.setAttribute('cursor', (angle % 180 === 0) ? 'row-resize' : 'col-resize'); + + var viewPoint = view.getClosestPoint(new g.Point(x, y)); + var line = this.childNodes.line; + line.setAttribute('x1', x); + line.setAttribute('y1', y); + line.setAttribute('x2', viewPoint.x); + line.setAttribute('y2', viewPoint.y); + }, + onPointerDown: function(evt) { + this.trigger('change:start', this, evt); + evt.stopPropagation(); + this.options.paper.undelegateEvents(); + this.delegateDocumentEvents(null, evt.data); + }, + onPointerMove: function(evt) { + this.trigger('changing', this, evt); + }, + onPointerUp: function(evt) { + this.undelegateDocumentEvents(); + this.options.paper.delegateEvents(); + this.trigger('change:end', this, evt); + }, + show: function() { + this.el.style.display = ''; + }, + hide: function() { + this.el.style.display = 'none'; + } + }); + + var Segments = ToolView.extend({ + name: 'segments', + precision: .5, + options: { + handleClass: SegmentHandle, + segmentLengthThreshold: 40, + redundancyRemoval: true, + anchor: getAnchor, + snapRadius: 10, + snapHandle: true + }, + handles: null, + onRender: function() { + this.resetHandles(); + var relatedView = this.relatedView; + var vertices = relatedView.model.vertices(); + vertices.unshift(relatedView.sourcePoint); + vertices.push(relatedView.targetPoint); + for (var i = 0, n = vertices.length; i < n - 1; i++) { + var vertex = vertices[i]; + var nextVertex = vertices[i + 1]; + var handle = this.renderHandle(vertex, nextVertex); + this.simulateRelatedView(handle.el); + this.handles.push(handle); + handle.options.index = i; + } + return this; + }, + renderHandle: function(vertex, nextVertex) { + var handle = new (this.options.handleClass)({ paper: this.paper }); + handle.render(); + this.updateHandle(handle, vertex, nextVertex); + handle.vel.appendTo(this.el); + this.startHandleListening(handle); + return handle; + }, + update: function() { + this.render(); + return this; + }, + startHandleListening: function(handle) { + this.listenTo(handle, 'change:start', this.onHandleChangeStart); + this.listenTo(handle, 'changing', this.onHandleChanging); + this.listenTo(handle, 'change:end', this.onHandleChangeEnd); + }, + resetHandles: function() { + var handles = this.handles; + this.handles = []; + this.stopListening(); + if (!Array.isArray(handles)) return; + for (var i = 0, n = handles.length; i < n; i++) { + handles[i].remove(); + } + }, + shiftHandleIndexes: function(value) { + var handles = this.handles; + for (var i = 0, n = handles.length; i < n; i++) handles[i].options.index += value; + }, + resetAnchor: function(type, anchor) { + var relatedModel = this.relatedView.model; + if (anchor) { + relatedModel.prop([type, 'anchor'], anchor, { + rewrite: true, + ui: true, + tool: this.cid + }); + } else { + relatedModel.removeProp([type, 'anchor'], { + ui: true, + tool: this.cid + }); + } + }, + snapHandle: function(handle, position, data) { + + var index = handle.options.index; + var linkView = this.relatedView; + var link = linkView.model; + var vertices = link.vertices(); + var axis = handle.options.axis; + var prev = vertices[index - 2] || data.sourceAnchor; + var next = vertices[index + 1] || data.targetAnchor; + var snapRadius = this.options.snapRadius; + if (Math.abs(position[axis] - prev[axis]) < snapRadius) { + position[axis] = prev[axis]; + } else if (Math.abs(position[axis] - next[axis]) < snapRadius) { + position[axis] = next[axis]; + } + return position; + }, + + onHandleChanging: function(handle, evt) { + + var data = this.eventData(evt); + var relatedView = this.relatedView; + var paper = relatedView.paper; + var index = handle.options.index - 1; + var coords = paper.snapToGrid(evt.clientX, evt.clientY); + var position = this.snapHandle(handle, coords.clone(), data); + var axis = handle.options.axis; + var offset = (this.options.snapHandle) ? 0 : (coords[axis] - position[axis]); + var link = relatedView.model; + var vertices = util.cloneDeep(link.vertices()); + var vertex = vertices[index]; + var nextVertex = vertices[index + 1]; + var anchorFn = this.options.anchor; + if (typeof anchorFn !== 'function') anchorFn = null; + + // First Segment + var sourceView = relatedView.sourceView; + var sourceBBox = relatedView.sourceBBox; + var changeSourceAnchor = false; + var deleteSourceAnchor = false; + if (!vertex) { + vertex = relatedView.sourceAnchor.toJSON(); + vertex[axis] = position[axis]; + if (sourceBBox.containsPoint(vertex)) { + vertex[axis] = position[axis]; + changeSourceAnchor = true; + } else { + // we left the area of the source magnet for the first time + vertices.unshift(vertex); + this.shiftHandleIndexes(1); + delateSourceAnchor = true; + } + } else if (index === 0) { + if (sourceBBox.containsPoint(vertex)) { + vertices.shift(); + this.shiftHandleIndexes(-1); + changeSourceAnchor = true; + } else { + vertex[axis] = position[axis]; + deleteSourceAnchor = true; + } + } else { + vertex[axis] = position[axis]; + } + + if (anchorFn && sourceView) { + if (changeSourceAnchor) { + var sourceAnchorPosition = data.sourceAnchor.clone(); + sourceAnchorPosition[axis] = position[axis]; + var sourceAnchor = anchorFn.call(relatedView, sourceAnchorPosition, sourceView, relatedView.sourceMagnet || sourceView.el, 'source', relatedView); + this.resetAnchor('source', sourceAnchor); + } + if (deleteSourceAnchor) { + this.resetAnchor('source', data.sourceAnchorDef); + } + } + + // Last segment + var targetView = relatedView.targetView; + var targetBBox = relatedView.targetBBox; + var changeTargetAnchor = false; + var deleteTargetAnchor = false; + if (!nextVertex) { + nextVertex = relatedView.targetAnchor.toJSON(); + nextVertex[axis] = position[axis]; + if (targetBBox.containsPoint(nextVertex)) { + changeTargetAnchor = true; + } else { + // we left the area of the target magnet for the first time + vertices.push(nextVertex); + deleteTargetAnchor = true; + } + } else if (index === vertices.length - 2) { + if (targetBBox.containsPoint(nextVertex)) { + vertices.pop(); + changeTargetAnchor = true; + } else { + nextVertex[axis] = position[axis]; + deleteTargetAnchor = true; + } + } else { + nextVertex[axis] = position[axis]; + } + + if (anchorFn && targetView) { + if (changeTargetAnchor) { + var targetAnchorPosition = data.targetAnchor.clone(); + targetAnchorPosition[axis] = position[axis]; + var targetAnchor = anchorFn.call(relatedView, targetAnchorPosition, targetView, relatedView.targetMagnet || targetView.el, 'target', relatedView); + this.resetAnchor('target', targetAnchor); + } + if (deleteTargetAnchor) { + this.resetAnchor('target', data.targetAnchorDef); + } + } + + link.vertices(vertices, { ui: true, tool: this.cid }); + this.updateHandle(handle, vertex, nextVertex, offset); + }, + onHandleChangeStart: function(handle, evt) { + var index = handle.options.index; + var handles = this.handles; + if (!Array.isArray(handles)) return; + for (var i = 0, n = handles.length; i < n; i++) { + if (i !== index) handles[i].hide() + } + this.focus(); + var relatedView = this.relatedView; + var relatedModel = relatedView.model; + this.eventData(evt, { + sourceAnchor: relatedView.sourceAnchor.clone(), + targetAnchor: relatedView.targetAnchor.clone(), + sourceAnchorDef: util.clone(relatedModel.prop(['source', 'anchor'])), + targetAnchorDef: util.clone(relatedModel.prop(['target', 'anchor'])) + }); + relatedView.model.startBatch('segment-move', { ui: true, tool: this.cid }); + }, + onHandleChangeEnd: function(handle) { + var linkView = this.relatedView; + if (this.options.redundancyRemoval) { + linkView.removeRedundantLinearVertices({ ui: true, tool: this.cid }); + } + this.render(); + this.blur(); + linkView.model.stopBatch('segment-move', { ui: true, tool: this.cid }); + }, + updateHandle: function(handle, vertex, nextVertex, offset) { + var vertical = Math.abs(vertex.x - nextVertex.x) < this.precision; + var horizontal = Math.abs(vertex.y - nextVertex.y) < this.precision; + if (vertical || horizontal) { + var segmentLine = new g.Line(vertex, nextVertex); + var length = segmentLine.length(); + if (length < this.options.segmentLengthThreshold) { + handle.hide(); + } else { + var position = segmentLine.midpoint() + var axis = (vertical) ? 'x' : 'y'; + position[axis] += offset || 0; + var angle = segmentLine.vector().vectorAngle(new g.Point(1, 0)); + handle.position(position.x, position.y, angle, this.relatedView); + handle.show(); + handle.options.axis = axis; + } + } else { + handle.hide(); + } + }, + onRemove: function() { + this.resetHandles(); + } + }, { + SegmentHandle: SegmentHandle // keep as class property + }); + + // End Markers + var Arrowhead = ToolView.extend({ + tagName: 'path', + xAxisVector: new g.Point(1, 0), + events: { + mousedown: 'onPointerDown', + touchstart: 'onPointerDown' + }, + documentEvents: { + mousemove: 'onPointerMove', + touchmove: 'onPointerMove', + mouseup: 'onPointerUp', + touchend: 'onPointerUp' + }, + onRender: function() { + this.update() + }, + update: function() { + var ratio = this.ratio; + var view = this.relatedView; + var tangent = view.getTangentAtRatio(ratio); + var position, angle; + if (tangent) { + position = tangent.start; + angle = tangent.vector().vectorAngle(this.xAxisVector) || 0; + } else { + position = view.getPointAtRatio(ratio); + angle = 0; + } + var matrix = V.createSVGMatrix().translate(position.x, position.y).rotate(angle); + this.vel.transform(matrix, { absolute: true }); + return this; + }, + onPointerDown: function(evt) { + evt.stopPropagation(); + var relatedView = this.relatedView; + relatedView.model.startBatch('arrowhead-move', { ui: true, tool: this.cid }); + if (relatedView.can('arrowheadMove')) { + relatedView.startArrowheadMove(this.arrowheadType); + this.delegateDocumentEvents(); + relatedView.paper.undelegateEvents(); + } + this.focus(); + this.el.style.pointerEvents = 'none'; + }, + onPointerMove: function(evt) { + var coords = this.paper.snapToGrid(evt.clientX, evt.clientY); + this.relatedView.pointermove(evt, coords.x, coords.y); + }, + onPointerUp: function(evt) { + this.undelegateDocumentEvents(); + var relatedView = this.relatedView; + var paper = relatedView.paper; + var coords = paper.snapToGrid(evt.clientX, evt.clientY); + relatedView.pointerup(evt, coords.x, coords.y); + paper.delegateEvents(); + this.blur(); + this.el.style.pointerEvents = ''; + relatedView.model.stopBatch('arrowhead-move', { ui: true, tool: this.cid }); + } + }); + + var TargetArrowhead = Arrowhead.extend({ + name: 'target-arrowhead', + ratio: 1, + arrowheadType: 'target', + attributes: { + 'd': 'M -10 -8 10 0 -10 8 Z', + 'fill': '#33334F', + 'stroke': '#FFFFFF', + 'stroke-width': 2, + 'cursor': 'move', + 'class': 'target-arrowhead' + } + }); + + var SourceArrowhead = Arrowhead.extend({ + name: 'source-arrowhead', + ratio: 0, + arrowheadType: 'source', + attributes: { + 'd': 'M 10 -8 -10 0 10 8 Z', + 'fill': '#33334F', + 'stroke': '#FFFFFF', + 'stroke-width': 2, + 'cursor': 'move', + 'class': 'source-arrowhead' + } + }); + + var Button = ToolView.extend({ + name: 'button', + events: { + 'mousedown': 'onPointerDown', + 'touchstart': 'onPointerDown' + }, + options: { + distance: 0, + offset: 0, + rotate: false + }, + onRender: function() { + this.renderChildren(this.options.markup); + this.update() + }, + update: function() { + var tangent, position, angle; + var distance = this.options.distance || 0; + if (util.isPercentage(distance)) { + tangent = this.relatedView.getTangentAtRatio(parseFloat(distance) / 100); + } else { + tangent = this.relatedView.getTangentAtLength(distance) + } + if (tangent) { + position = tangent.start; + angle = tangent.vector().vectorAngle(new g.Point(1,0)) || 0; + } else { + position = this.relatedView.getConnection().start; + angle = 0; + } + var matrix = V.createSVGMatrix() + .translate(position.x, position.y) + .rotate(angle) + .translate(0, this.options.offset || 0); + if (!this.options.rotate) matrix = matrix.rotate(-angle); + this.vel.transform(matrix, { absolute: true }); + return this; + }, + onPointerDown: function(evt) { + evt.stopPropagation(); + var actionFn = this.options.action; + if (typeof actionFn === 'function') { + actionFn.call(this.relatedView, evt, this.relatedView); + } + } + }); + + + var Remove = Button.extend({ + children: [{ + tagName: 'circle', + selector: 'button', + attributes: { + 'r': 7, + 'fill': '#FF1D00', + 'cursor': 'pointer' + } + }, { + tagName: 'path', + selector: 'icon', + attributes: { + 'd': 'M -3 -3 3 3 M -3 3 3 -3', + 'fill': 'none', + 'stroke': '#FFFFFF', + 'stroke-width': 2, + 'pointer-events': 'none' + } + }], + options: { + distance: 60, + offset: 0, + action: function(evt) { + this.model.remove({ ui: true, tool: this.cid }); + } + } + }); + + var Boundary = ToolView.extend({ + name: 'boundary', + tagName: 'rect', + options: { + padding: 10 + }, + attributes: { + 'fill': 'none', + 'stroke': '#33334F', + 'stroke-width': .5, + 'stroke-dasharray': '5, 5', + 'pointer-events': 'none' + }, + onRender: function() { + this.update(); + }, + update: function() { + var padding = this.options.padding; + if (!isFinite(padding)) padding = 0; + var bbox = this.relatedView.getConnection().bbox().inflate(padding); + this.vel.attr(bbox.toJSON()); + return this; + } + }); + + var Anchor = ToolView.extend({ + tagName: 'g', + type: null, + children: [{ + tagName: 'circle', + selector: 'anchor', + attributes: { + 'cursor': 'pointer' + } + }, { + tagName: 'rect', + selector: 'area', + attributes: { + 'pointer-events': 'none', + 'fill': 'none', + 'stroke': '#33334F', + 'stroke-dasharray': '2,4', + 'rx': 5, + 'ry': 5 + } + }], + events: { + mousedown: 'onPointerDown', + touchstart: 'onPointerDown', + dblclick: 'onPointerDblClick' + }, + documentEvents: { + mousemove: 'onPointerMove', + touchmove: 'onPointerMove', + mouseup: 'onPointerUp', + touchend: 'onPointerUp' + }, + options: { + snap: snapAnchor, + anchor: getAnchor, + customAnchorAttributes: { + 'stroke-width': 4, + 'stroke': '#33334F', + 'fill': '#FFFFFF', + 'r': 5 + }, + defaultAnchorAttributes: { + 'stroke-width': 2, + 'stroke': '#FFFFFF', + 'fill': '#33334F', + 'r': 6 + }, + areaPadding: 6, + snapRadius: 10, + restrictArea: true, + redundancyRemoval: true + }, + onRender: function() { + this.renderChildren(); + this.toggleArea(false); + this.update(); + }, + update: function() { + var type = this.type; + var relatedView = this.relatedView; + var view = relatedView.getEndView(type); + if (view) { + this.updateAnchor(); + this.updateArea(); + this.el.style.display = ''; + } else { + this.el.style.display = 'none'; + } + return this; + }, + updateAnchor: function() { + var childNodes = this.childNodes; + if (!childNodes) return; + var anchorNode = childNodes.anchor; + if (!anchorNode) return; + var relatedView = this.relatedView; + var type = this.type; + var position = relatedView.getEndAnchor(type); + var options = this.options; + var customAnchor = relatedView.model.prop([type, 'anchor']); + anchorNode.setAttribute('transform', 'translate(' + position.x + ',' + position.y + ')'); + var anchorAttributes = (customAnchor) ? options.customAnchorAttributes : options.defaultAnchorAttributes; + for (var attrName in anchorAttributes) { + anchorNode.setAttribute(attrName, anchorAttributes[attrName]); + } + }, + updateArea: function() { + var childNodes = this.childNodes; + if (!childNodes) return; + var areaNode = childNodes.area; + if (!areaNode) return; + var relatedView = this.relatedView; + var type = this.type; + var view = relatedView.getEndView(type); + var magnet = relatedView.getEndMagnet(type); + var padding = this.options.areaPadding; + if (!isFinite(padding)) padding = 0; + var bbox = view.getNodeUnrotatedBBox(magnet).inflate(padding); + var angle = view.model.angle(); + areaNode.setAttribute('x', -bbox.width / 2); + areaNode.setAttribute('y', -bbox.height / 2); + areaNode.setAttribute('width', bbox.width); + areaNode.setAttribute('height', bbox.height); + var origin = view.model.getBBox().center(); + var center = bbox.center().rotate(origin, -angle) + areaNode.setAttribute('transform', 'translate(' + center.x + ',' + center.y + ') rotate(' + angle +')'); + }, + toggleArea: function(visible) { + this.childNodes.area.style.display = (visible) ? '' : 'none'; + }, + onPointerDown: function(evt) { + evt.stopPropagation(); + this.paper.undelegateEvents(); + this.delegateDocumentEvents(); + this.focus(); + this.toggleArea(this.options.restrictArea); + this.relatedView.model.startBatch('anchor-move', { ui: true, tool: this.cid }); + }, + resetAnchor: function(anchor) { + var type = this.type; + var relatedModel = this.relatedView.model; + if (anchor) { + relatedModel.prop([type, 'anchor'], anchor, { + rewrite: true, + ui: true, + tool: this.cid + }); + } else { + relatedModel.removeProp([type, 'anchor'], { + ui: true, + tool: this.cid + }); + } + }, + onPointerMove: function(evt) { + + var relatedView = this.relatedView; + var type = this.type; + var view = relatedView.getEndView(type); + var magnet = relatedView.getEndMagnet(type); + + var coords = this.paper.clientToLocalPoint(evt.clientX, evt.clientY); + var snapFn = this.options.snap; + if (typeof snapFn === 'function') { + coords = snapFn.call(relatedView, coords, view, magnet, type, relatedView, this); + coords = new g.Point(coords); + } + + if (this.options.restrictArea) { + // snap coords within node bbox + var bbox = view.getNodeUnrotatedBBox(magnet); + var angle = view.model.angle(); + var origin = view.model.getBBox().center(); + var rotatedCoords = coords.clone().rotate(origin, angle); + if (!bbox.containsPoint(rotatedCoords)) { + coords = bbox.pointNearestToPoint(rotatedCoords).rotate(origin, -angle); + } + } + + var anchor; + var anchorFn = this.options.anchor; + if (typeof anchorFn === 'function') { + anchor = anchorFn.call(relatedView, coords, view, magnet, type, relatedView); + } + + this.resetAnchor(anchor); + this.update(); + }, + + onPointerUp: function(evt) { + this.paper.delegateEvents(); + this.undelegateDocumentEvents(); + this.blur(); + this.toggleArea(false); + var linkView = this.relatedView; + if (this.options.redundancyRemoval) linkView.removeRedundantLinearVertices({ ui: true, tool: this.cid }); + linkView.model.stopBatch('anchor-move', { ui: true, tool: this.cid }); + }, + + onPointerDblClick: function() { + this.resetAnchor(); + this.update(); + } + }); + + var SourceAnchor = Anchor.extend({ + name: 'source-anchor', + type: 'source' + }); + + var TargetAnchor = Anchor.extend({ + name: 'target-anchor', + type: 'target' + }); + + // Export + joint.linkTools = { + Vertices: Vertices, + Segments: Segments, + SourceArrowhead: SourceArrowhead, + TargetArrowhead: TargetArrowhead, + SourceAnchor: SourceAnchor, + TargetAnchor: TargetAnchor, + Button: Button, + Remove: Remove, + Boundary: Boundary + }; + +})(joint, joint.util, V, g); + +joint.dia.Element.define('erd.Entity', { + size: { width: 150, height: 60 }, + attrs: { + '.outer': { + fill: '#2ECC71', stroke: '#27AE60', 'stroke-width': 2, + points: '100,0 100,60 0,60 0,0' + }, + '.inner': { + fill: '#2ECC71', stroke: '#27AE60', 'stroke-width': 2, + points: '95,5 95,55 5,55 5,5', + display: 'none' + }, + text: { + text: 'Entity', + 'font-family': 'Arial', 'font-size': 14, + 'ref-x': .5, 'ref-y': .5, + 'y-alignment': 'middle', 'text-anchor': 'middle' + } + } +}, { + markup: '', +}); + + +joint.shapes.erd.Entity.define('erd.WeakEntity', { + attrs: { + '.inner': { display: 'auto' }, + text: { text: 'Weak Entity' } + } +}); + +joint.dia.Element.define('erd.Relationship', { + size: { width: 80, height: 80 }, + attrs: { + '.outer': { + fill: '#3498DB', stroke: '#2980B9', 'stroke-width': 2, + points: '40,0 80,40 40,80 0,40' + }, + '.inner': { + fill: '#3498DB', stroke: '#2980B9', 'stroke-width': 2, + points: '40,5 75,40 40,75 5,40', + display: 'none' + }, + text: { + text: 'Relationship', + 'font-family': 'Arial', 'font-size': 12, + 'ref-x': .5, 'ref-y': .5, + 'y-alignment': 'middle', 'text-anchor': 'middle' + } + } +}, { + markup: '', +}); + +joint.shapes.erd.Relationship.define('erd.IdentifyingRelationship', { + attrs: { + '.inner': { display: 'auto' }, + text: { text: 'Identifying' } + } +}); + +joint.dia.Element.define('erd.Attribute', { + size: { width: 100, height: 50 }, + attrs: { + 'ellipse': { + transform: 'translate(50, 25)' + }, + '.outer': { + stroke: '#D35400', 'stroke-width': 2, + cx: 0, cy: 0, rx: 50, ry: 25, + fill: '#E67E22' + }, + '.inner': { + stroke: '#D35400', 'stroke-width': 2, + cx: 0, cy: 0, rx: 45, ry: 20, + fill: '#E67E22', display: 'none' + }, + text: { + 'font-family': 'Arial', 'font-size': 14, + 'ref-x': .5, 'ref-y': .5, + 'y-alignment': 'middle', 'text-anchor': 'middle' + } + } +}, { + markup: '', +}); + +joint.shapes.erd.Attribute.define('erd.Multivalued', { + attrs: { + '.inner': { display: 'block' }, + text: { text: 'multivalued' } + } +}); + +joint.shapes.erd.Attribute.define('erd.Derived', { + attrs: { + '.outer': { 'stroke-dasharray': '3,5' }, + text: { text: 'derived' } + } +}); + +joint.shapes.erd.Attribute.define('erd.Key', { + attrs: { + ellipse: { 'stroke-width': 4 }, + text: { text: 'key', 'font-weight': '800', 'text-decoration': 'underline' } + } +}); + +joint.shapes.erd.Attribute.define('erd.Normal', { + attrs: { text: { text: 'Normal' } } +}); + +joint.dia.Element.define('erd.ISA', { + type: 'erd.ISA', + size: { width: 100, height: 50 }, + attrs: { + polygon: { + points: '0,0 50,50 100,0', + fill: '#F1C40F', stroke: '#F39C12', 'stroke-width': 2 + }, + text: { + text: 'ISA', 'font-size': 18, + 'ref-x': .5, 'ref-y': .3, + 'y-alignment': 'middle', 'text-anchor': 'middle' + } + } +}, { + markup: '', +}); + +joint.dia.Link.define('erd.Line', {}, { + cardinality: function(value) { + this.set('labels', [{ position: -20, attrs: { text: { dy: -8, text: value } } }]); + } +}); + +joint.shapes.basic.Circle.define('fsa.State', { + attrs: { + circle: { 'stroke-width': 3 }, + text: { 'font-weight': '800' } + } +}); + +joint.dia.Element.define('fsa.StartState', { + size: { width: 20, height: 20 }, + attrs: { + circle: { + transform: 'translate(10, 10)', + r: 10, + fill: '#000000' + } + } +}, { + markup: '', +}); + +joint.dia.Element.define('fsa.EndState', { + size: { width: 20, height: 20 }, + attrs: { + '.outer': { + transform: 'translate(10, 10)', + r: 10, + fill: '#ffffff', + stroke: '#000000' + }, + + '.inner': { + transform: 'translate(10, 10)', + r: 6, + fill: '#000000' + } + } +}, { + markup: '', +}); + +joint.dia.Link.define('fsa.Arrow', { + attrs: { '.marker-target': { d: 'M 10 0 L 0 5 L 10 10 z' } }, + smooth: true +}); + +joint.dia.Element.define('org.Member', { + size: { width: 180, height: 70 }, + attrs: { + rect: { width: 170, height: 60 }, + + '.card': { + fill: '#FFFFFF', stroke: '#000000', 'stroke-width': 2, + 'pointer-events': 'visiblePainted', rx: 10, ry: 10 + }, + + image: { + width: 48, height: 48, + ref: '.card', 'ref-x': 10, 'ref-y': 5 + }, + + '.rank': { + 'text-decoration': 'underline', + ref: '.card', 'ref-x': 0.9, 'ref-y': 0.2, + 'font-family': 'Courier New', 'font-size': 14, + 'text-anchor': 'end' + }, + + '.name': { + 'font-weight': '800', + ref: '.card', 'ref-x': 0.9, 'ref-y': 0.6, + 'font-family': 'Courier New', 'font-size': 14, + 'text-anchor': 'end' + } + } +}, { + markup: '', +}); + +joint.dia.Link.define('org.Arrow', { + source: { selector: '.card' }, target: { selector: '.card' }, + attrs: { '.connection': { stroke: '#585858', 'stroke-width': 3 } }, + z: -1 +}); + +joint.shapes.basic.Generic.define('chess.KingWhite', { + size: { width: 42, height: 38 } +}, { + markup: ' ' +}); + +joint.shapes.basic.Generic.define('chess.KingBlack', { + size: { width: 42, height: 38 } +}, { + markup: ' ' +}); + +joint.shapes.basic.Generic.define('chess.QueenWhite', { + size: { width: 42, height: 38 } +}, { + markup: ' ' +}); + +joint.shapes.basic.Generic.define('chess.QueenBlack', { + size: { width: 42, height: 38 } +}, { + markup: ' ' +}); + +joint.shapes.basic.Generic.define('chess.RookWhite', { + size: { width: 32, height: 34 } +}, { + markup: ' ' +}); + +joint.shapes.basic.Generic.define('chess.RookBlack', { + size: { width: 32, height: 34 } +}, { + markup: ' ' +}); + +joint.shapes.basic.Generic.define('chess.BishopWhite', { + size: { width: 38, height: 38 } +}, { + markup: ' ' +}); + +joint.shapes.basic.Generic.define('chess.BishopBlack', { + size: { width: 38, height: 38 } +}, { + markup: ' ' +}); + +joint.shapes.basic.Generic.define('chess.KnightWhite', { + size: { width: 38, height: 37 } +}, { + markup: ' ' +}); + +joint.shapes.basic.Generic.define('chess.KnightBlack', { + size: { width: 38, height: 37 } +}, { + markup: ' ' +}); + +joint.shapes.basic.Generic.define('chess.PawnWhite', { + size: { width: 28, height: 33 } +}, { + markup: '' +}); + +joint.shapes.basic.Generic.define('chess.PawnBlack', { + size: { width: 28, height: 33 } +}, { + markup: '' +}); + +joint.shapes.basic.Generic.define('pn.Place', { + size: { width: 50, height: 50 }, + attrs: { + '.root': { + r: 25, + fill: '#ffffff', + stroke: '#000000', + transform: 'translate(25, 25)' + }, + '.label': { + 'text-anchor': 'middle', + 'ref-x': .5, + 'ref-y': -20, + ref: '.root', + fill: '#000000', + 'font-size': 12 + }, + '.tokens > circle': { + fill: '#000000', + r: 5 + }, + '.tokens.one > circle': { transform: 'translate(25, 25)' }, + + '.tokens.two > circle:nth-child(1)': { transform: 'translate(19, 25)' }, + '.tokens.two > circle:nth-child(2)': { transform: 'translate(31, 25)' }, + + '.tokens.three > circle:nth-child(1)': { transform: 'translate(18, 29)' }, + '.tokens.three > circle:nth-child(2)': { transform: 'translate(25, 19)' }, + '.tokens.three > circle:nth-child(3)': { transform: 'translate(32, 29)' }, + + '.tokens.alot > text': { + transform: 'translate(25, 18)', + 'text-anchor': 'middle', + fill: '#000000' + } + } +}, { + markup: '', +}); + +joint.shapes.pn.PlaceView = joint.dia.ElementView.extend({}, { + + initialize: function() { + + joint.dia.ElementView.prototype.initialize.apply(this, arguments); + + this.model.on('change:tokens', function() { + + this.renderTokens(); + this.update(); + + }, this); + }, + + render: function() { + + joint.dia.ElementView.prototype.render.apply(this, arguments); + + this.renderTokens(); + this.update(); + }, + + renderTokens: function() { + + var $tokens = this.$('.tokens').empty(); + $tokens[0].className.baseVal = 'tokens'; + + var tokens = this.model.get('tokens'); + + if (!tokens) return; + + switch (tokens) { + + case 1: + $tokens[0].className.baseVal += ' one'; + $tokens.append(V('').node); + break; + + case 2: + $tokens[0].className.baseVal += ' two'; + $tokens.append(V('').node, V('').node); + break; + + case 3: + $tokens[0].className.baseVal += ' three'; + $tokens.append(V('').node, V('').node, V('').node); + break; + + default: + $tokens[0].className.baseVal += ' alot'; + $tokens.append(V('').text(tokens + '').node); + break; + } + } +}); + +joint.shapes.basic.Generic.define('pn.Transition', { + size: { width: 12, height: 50 }, + attrs: { + 'rect': { + width: 12, + height: 50, + fill: '#000000', + stroke: '#000000' + }, + '.label': { + 'text-anchor': 'middle', + 'ref-x': .5, + 'ref-y': -20, + ref: 'rect', + fill: '#000000', + 'font-size': 12 + } + } +}, { + markup: '', +}); + +joint.dia.Link.define('pn.Link', { + attrs: { '.marker-target': { d: 'M 10 0 L 0 5 L 10 10 z' } } +}); + +/** + * @deprecated use the port api insteac + */ +joint.shapes.basic.Generic.define('devs.Model', { + inPorts: [], + outPorts: [], + size: { + width: 80, + height: 80 + }, + attrs: { + '.': { + magnet: false + }, + '.label': { + text: 'Model', + 'ref-x': .5, + 'ref-y': 10, + 'font-size': 18, + 'text-anchor': 'middle', + fill: '#000' + }, + '.body': { + 'ref-width': '100%', + 'ref-height': '100%', + stroke: '#000' + } + }, + ports: { + groups: { + 'in': { + position: { + name: 'left' + }, + attrs: { + '.port-label': { + fill: '#000' + }, + '.port-body': { + fill: '#fff', + stroke: '#000', + r: 10, + magnet: true + } + }, + label: { + position: { + name: 'left', + args: { + y: 10 + } + } + } + }, + 'out': { + position: { + name: 'right' + }, + attrs: { + '.port-label': { + fill: '#000' + }, + '.port-body': { + fill: '#fff', + stroke: '#000', + r: 10, + magnet: true + } + }, + label: { + position: { + name: 'right', + args: { + y: 10 + } + } + } + } + } + } +}, { + markup: '', + portMarkup: '', + portLabelMarkup: '', + + initialize: function() { + + joint.shapes.basic.Generic.prototype.initialize.apply(this, arguments); + + this.on('change:inPorts change:outPorts', this.updatePortItems, this); + this.updatePortItems(); + }, + + updatePortItems: function(model, changed, opt) { + + // Make sure all ports are unique. + var inPorts = joint.util.uniq(this.get('inPorts')); + var outPorts = joint.util.difference(joint.util.uniq(this.get('outPorts')), inPorts); + + var inPortItems = this.createPortItems('in', inPorts); + var outPortItems = this.createPortItems('out', outPorts); + + this.prop('ports/items', inPortItems.concat(outPortItems), joint.util.assign({ rewrite: true }, opt)); + }, + + createPortItem: function(group, port) { + + return { + id: port, + group: group, + attrs: { + '.port-label': { + text: port + } + } + }; + }, + + createPortItems: function(group, ports) { + + return joint.util.toArray(ports).map(this.createPortItem.bind(this, group)); + }, + + _addGroupPort: function(port, group, opt) { + + var ports = this.get(group); + return this.set(group, Array.isArray(ports) ? ports.concat(port) : [port], opt); + }, + + addOutPort: function(port, opt) { + + return this._addGroupPort(port, 'outPorts', opt); + }, + + addInPort: function(port, opt) { + + return this._addGroupPort(port, 'inPorts', opt); + }, + + _removeGroupPort: function(port, group, opt) { + + return this.set(group, joint.util.without(this.get(group), port), opt); + }, + + removeOutPort: function(port, opt) { + + return this._removeGroupPort(port, 'outPorts', opt); + }, + + removeInPort: function(port, opt) { + + return this._removeGroupPort(port, 'inPorts', opt); + }, + + _changeGroup: function(group, properties, opt) { + + return this.prop('ports/groups/' + group, joint.util.isObject(properties) ? properties : {}, opt); + }, + + changeInGroup: function(properties, opt) { + + return this._changeGroup('in', properties, opt); + }, + + changeOutGroup: function(properties, opt) { + + return this._changeGroup('out', properties, opt); + } +}); + +joint.shapes.devs.Model.define('devs.Atomic', { + size: { + width: 80, + height: 80 + }, + attrs: { + '.label': { + text: 'Atomic' + } + } +}); + +joint.shapes.devs.Model.define('devs.Coupled', { + size: { + width: 200, + height: 300 + }, + attrs: { + '.label': { + text: 'Coupled' + } + } +}); + +joint.dia.Link.define('devs.Link', { + attrs: { + '.connection': { + 'stroke-width': 2 + } + } +}); + +joint.shapes.basic.Generic.define('uml.Class', { + attrs: { + rect: { 'width': 200 }, + + '.uml-class-name-rect': { 'stroke': 'black', 'stroke-width': 2, 'fill': '#3498db' }, + '.uml-class-attrs-rect': { 'stroke': 'black', 'stroke-width': 2, 'fill': '#2980b9' }, + '.uml-class-methods-rect': { 'stroke': 'black', 'stroke-width': 2, 'fill': '#2980b9' }, + + '.uml-class-name-text': { + 'ref': '.uml-class-name-rect', + 'ref-y': .5, + 'ref-x': .5, + 'text-anchor': 'middle', + 'y-alignment': 'middle', + 'font-weight': 'bold', + 'fill': 'black', + 'font-size': 12, + 'font-family': 'Times New Roman' + }, + '.uml-class-attrs-text': { + 'ref': '.uml-class-attrs-rect', 'ref-y': 5, 'ref-x': 5, + 'fill': 'black', 'font-size': 12, 'font-family': 'Times New Roman' + }, + '.uml-class-methods-text': { + 'ref': '.uml-class-methods-rect', 'ref-y': 5, 'ref-x': 5, + 'fill': 'black', 'font-size': 12, 'font-family': 'Times New Roman' + } + }, + + name: [], + attributes: [], + methods: [] +}, { + markup: [ + '', + '', + '', + '', + '', + '' + ].join(''), + + initialize: function() { + + this.on('change:name change:attributes change:methods', function() { + this.updateRectangles(); + this.trigger('uml-update'); + }, this); + + this.updateRectangles(); + + joint.shapes.basic.Generic.prototype.initialize.apply(this, arguments); + }, + + getClassName: function() { + return this.get('name'); + }, + + updateRectangles: function() { + + var attrs = this.get('attrs'); + + var rects = [ + { type: 'name', text: this.getClassName() }, + { type: 'attrs', text: this.get('attributes') }, + { type: 'methods', text: this.get('methods') } + ]; + + var offsetY = 0; + + rects.forEach(function(rect) { + + var lines = Array.isArray(rect.text) ? rect.text : [rect.text]; + var rectHeight = lines.length * 20 + 20; + + attrs['.uml-class-' + rect.type + '-text'].text = lines.join('\n'); + attrs['.uml-class-' + rect.type + '-rect'].height = rectHeight; + attrs['.uml-class-' + rect.type + '-rect'].transform = 'translate(0,' + offsetY + ')'; + + offsetY += rectHeight; + }); + } + +}); + +joint.shapes.uml.ClassView = joint.dia.ElementView.extend({ + + initialize: function() { + + joint.dia.ElementView.prototype.initialize.apply(this, arguments); + + this.listenTo(this.model, 'uml-update', function() { + this.update(); + this.resize(); + }); + } +}); + +joint.shapes.uml.Class.define('uml.Abstract', { + attrs: { + '.uml-class-name-rect': { fill: '#e74c3c' }, + '.uml-class-attrs-rect': { fill: '#c0392b' }, + '.uml-class-methods-rect': { fill: '#c0392b' } + } +}, { + + getClassName: function() { + return ['<>', this.get('name')]; + } + +}); +joint.shapes.uml.AbstractView = joint.shapes.uml.ClassView; + +joint.shapes.uml.Class.define('uml.Interface', { + attrs: { + '.uml-class-name-rect': { fill: '#f1c40f' }, + '.uml-class-attrs-rect': { fill: '#f39c12' }, + '.uml-class-methods-rect': { fill: '#f39c12' } + } +}, { + getClassName: function() { + return ['<>', this.get('name')]; + } +}); +joint.shapes.uml.InterfaceView = joint.shapes.uml.ClassView; + +joint.dia.Link.define('uml.Generalization', { + attrs: { '.marker-target': { d: 'M 20 0 L 0 10 L 20 20 z', fill: 'white' } } +}); + +joint.dia.Link.define('uml.Implementation', { + attrs: { + '.marker-target': { d: 'M 20 0 L 0 10 L 20 20 z', fill: 'white' }, + '.connection': { 'stroke-dasharray': '3,3' } + } +}); + +joint.dia.Link.define('uml.Aggregation', { + attrs: { '.marker-target': { d: 'M 40 10 L 20 20 L 0 10 L 20 0 z', fill: 'white' } } +}); + +joint.dia.Link.define('uml.Composition', { + attrs: { '.marker-target': { d: 'M 40 10 L 20 20 L 0 10 L 20 0 z', fill: 'black' } } +}); + +joint.dia.Link.define('uml.Association'); + +// Statechart + +joint.shapes.basic.Generic.define('uml.State', { + attrs: { + '.uml-state-body': { + 'width': 200, 'height': 200, 'rx': 10, 'ry': 10, + 'fill': '#ecf0f1', 'stroke': '#bdc3c7', 'stroke-width': 3 + }, + '.uml-state-separator': { + 'stroke': '#bdc3c7', 'stroke-width': 2 + }, + '.uml-state-name': { + 'ref': '.uml-state-body', 'ref-x': .5, 'ref-y': 5, 'text-anchor': 'middle', + 'fill': '#000000', 'font-family': 'Courier New', 'font-size': 14 + }, + '.uml-state-events': { + 'ref': '.uml-state-separator', 'ref-x': 5, 'ref-y': 5, + 'fill': '#000000', 'font-family': 'Courier New', 'font-size': 14 + } + }, + + name: 'State', + events: [] + +}, { + markup: [ + '', + '', + '', + '', + '', + '', + '', + '' + ].join(''), + + initialize: function() { + + this.on({ + 'change:name': this.updateName, + 'change:events': this.updateEvents, + 'change:size': this.updatePath + }, this); + + this.updateName(); + this.updateEvents(); + this.updatePath(); + + joint.shapes.basic.Generic.prototype.initialize.apply(this, arguments); + }, + + updateName: function() { + + this.attr('.uml-state-name/text', this.get('name')); + }, + + updateEvents: function() { + + this.attr('.uml-state-events/text', this.get('events').join('\n')); + }, + + updatePath: function() { + + var d = 'M 0 20 L ' + this.get('size').width + ' 20'; + + // We are using `silent: true` here because updatePath() is meant to be called + // on resize and there's no need to to update the element twice (`change:size` + // triggers also an update). + this.attr('.uml-state-separator/d', d, { silent: true }); + } +}); + +joint.shapes.basic.Circle.define('uml.StartState', { + type: 'uml.StartState', + attrs: { circle: { 'fill': '#34495e', 'stroke': '#2c3e50', 'stroke-width': 2, 'rx': 1 } } +}); + +joint.shapes.basic.Generic.define('uml.EndState', { + size: { width: 20, height: 20 }, + attrs: { + 'circle.outer': { + transform: 'translate(10, 10)', + r: 10, + fill: '#ffffff', + stroke: '#2c3e50' + }, + + 'circle.inner': { + transform: 'translate(10, 10)', + r: 6, + fill: '#34495e' + } + } +}, { + markup: '', +}); + +joint.dia.Link.define('uml.Transition', { + attrs: { + '.marker-target': { d: 'M 10 0 L 0 5 L 10 10 z', fill: '#34495e', stroke: '#2c3e50' }, + '.connection': { stroke: '#2c3e50' } + } +}); + +joint.shapes.basic.Generic.define('logic.Gate', { + size: { width: 80, height: 40 }, + attrs: { + '.': { magnet: false }, + '.body': { width: 100, height: 50 }, + circle: { r: 7, stroke: 'black', fill: 'transparent', 'stroke-width': 2 } + } +}, { + operation: function() { + return true; + } +}); + +joint.shapes.logic.Gate.define('logic.IO', { + size: { width: 60, height: 30 }, + attrs: { + '.body': { fill: 'white', stroke: 'black', 'stroke-width': 2 }, + '.wire': { ref: '.body', 'ref-y': .5, stroke: 'black' }, + text: { + fill: 'black', + ref: '.body', 'ref-x': .5, 'ref-y': .5, 'y-alignment': 'middle', + 'text-anchor': 'middle', + 'font-weight': 'bold', + 'font-variant': 'small-caps', + 'text-transform': 'capitalize', + 'font-size': '14px' + } + } +}, { + markup: '', +}); + +joint.shapes.logic.IO.define('logic.Input', { + attrs: { + '.wire': { 'ref-dx': 0, d: 'M 0 0 L 23 0' }, + circle: { ref: '.body', 'ref-dx': 30, 'ref-y': 0.5, magnet: true, 'class': 'output', port: 'out' }, + text: { text: 'input' } + } +}); + +joint.shapes.logic.IO.define('logic.Output', { + attrs: { + '.wire': { 'ref-x': 0, d: 'M 0 0 L -23 0' }, + circle: { ref: '.body', 'ref-x': -30, 'ref-y': 0.5, magnet: 'passive', 'class': 'input', port: 'in' }, + text: { text: 'output' } + } +}); + +joint.shapes.logic.Gate.define('logic.Gate11', { + attrs: { + '.input': { ref: '.body', 'ref-x': -2, 'ref-y': 0.5, magnet: 'passive', port: 'in' }, + '.output': { ref: '.body', 'ref-dx': 2, 'ref-y': 0.5, magnet: true, port: 'out' } + } +}, { + markup: '', +}); + +joint.shapes.logic.Gate.define('logic.Gate21', { + attrs: { + '.input1': { ref: '.body', 'ref-x': -2, 'ref-y': 0.3, magnet: 'passive', port: 'in1' }, + '.input2': { ref: '.body', 'ref-x': -2, 'ref-y': 0.7, magnet: 'passive', port: 'in2' }, + '.output': { ref: '.body', 'ref-dx': 2, 'ref-y': 0.5, magnet: true, port: 'out' } + } +}, { + markup: '', +}); + +joint.shapes.logic.Gate11.define('logic.Repeater', { + attrs: { image: { 'xlink:href': 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgo8c3ZnCiAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgeG1sbnM6Y2M9Imh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL25zIyIKICAgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIgogICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnNvZGlwb2RpPSJodHRwOi8vc29kaXBvZGkuc291cmNlZm9yZ2UubmV0L0RURC9zb2RpcG9kaS0wLmR0ZCIKICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiCiAgIHdpZHRoPSIxMDAiCiAgIGhlaWdodD0iNTAiCiAgIGlkPSJzdmcyIgogICBzb2RpcG9kaTp2ZXJzaW9uPSIwLjMyIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIwLjQ2IgogICB2ZXJzaW9uPSIxLjAiCiAgIHNvZGlwb2RpOmRvY25hbWU9Ik5PVCBBTlNJLnN2ZyIKICAgaW5rc2NhcGU6b3V0cHV0X2V4dGVuc2lvbj0ib3JnLmlua3NjYXBlLm91dHB1dC5zdmcuaW5rc2NhcGUiPgogIDxkZWZzCiAgICAgaWQ9ImRlZnM0Ij4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiAxNSA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF96PSI1MCA6IDE1IDogMSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIyNSA6IDEwIDogMSIKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI3MTQiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogMC41IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3o9IjEgOiAwLjUgOiAxIgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjAuNSA6IDAuMzMzMzMzMzMgOiAxIgogICAgICAgaWQ9InBlcnNwZWN0aXZlMjgwNiIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlMjgxOSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIzNzIuMDQ3MjQgOiAzNTAuNzg3MzkgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfej0iNzQ0LjA5NDQ4IDogNTI2LjE4MTA5IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA1MjYuMTgxMDkgOiAxIgogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlMjc3NyIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSI3NSA6IDQwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjE1MCA6IDYwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA2MCA6IDEiCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBpZD0icGVyc3BlY3RpdmUzMjc1IgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjUwIDogMzMuMzMzMzMzIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjEwMCA6IDUwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA1MCA6IDEiCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBpZD0icGVyc3BlY3RpdmU1NTMzIgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjMyIDogMjEuMzMzMzMzIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjY0IDogMzIgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfeT0iMCA6IDEwMDAgOiAwIgogICAgICAgaW5rc2NhcGU6dnBfeD0iMCA6IDMyIDogMSIKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI1NTciCiAgICAgICBpbmtzY2FwZTpwZXJzcDNkLW9yaWdpbj0iMjUgOiAxNi42NjY2NjcgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfej0iNTAgOiAyNSA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogMjUgOiAxIgogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIgLz4KICA8L2RlZnM+CiAgPHNvZGlwb2RpOm5hbWVkdmlldwogICAgIGlkPSJiYXNlIgogICAgIHBhZ2Vjb2xvcj0iI2ZmZmZmZiIKICAgICBib3JkZXJjb2xvcj0iIzY2NjY2NiIKICAgICBib3JkZXJvcGFjaXR5PSIxLjAiCiAgICAgaW5rc2NhcGU6cGFnZW9wYWNpdHk9IjAuMCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnpvb209IjgiCiAgICAgaW5rc2NhcGU6Y3g9Ijg0LjY4NTM1MiIKICAgICBpbmtzY2FwZTpjeT0iMTUuMjg4NjI4IgogICAgIGlua3NjYXBlOmRvY3VtZW50LXVuaXRzPSJweCIKICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJsYXllcjEiCiAgICAgc2hvd2dyaWQ9InRydWUiCiAgICAgaW5rc2NhcGU6Z3JpZC1iYm94PSJ0cnVlIgogICAgIGlua3NjYXBlOmdyaWQtcG9pbnRzPSJ0cnVlIgogICAgIGdyaWR0b2xlcmFuY2U9IjEwMDAwIgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTM5OSIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSI4NzQiCiAgICAgaW5rc2NhcGU6d2luZG93LXg9IjMzIgogICAgIGlua3NjYXBlOndpbmRvdy15PSIwIgogICAgIGlua3NjYXBlOnNuYXAtYmJveD0idHJ1ZSI+CiAgICA8aW5rc2NhcGU6Z3JpZAogICAgICAgaWQ9IkdyaWRGcm9tUHJlMDQ2U2V0dGluZ3MiCiAgICAgICB0eXBlPSJ4eWdyaWQiCiAgICAgICBvcmlnaW54PSIwcHgiCiAgICAgICBvcmlnaW55PSIwcHgiCiAgICAgICBzcGFjaW5neD0iMXB4IgogICAgICAgc3BhY2luZ3k9IjFweCIKICAgICAgIGNvbG9yPSIjMDAwMGZmIgogICAgICAgZW1wY29sb3I9IiMwMDAwZmYiCiAgICAgICBvcGFjaXR5PSIwLjIiCiAgICAgICBlbXBvcGFjaXR5PSIwLjQiCiAgICAgICBlbXBzcGFjaW5nPSI1IgogICAgICAgdmlzaWJsZT0idHJ1ZSIKICAgICAgIGVuYWJsZWQ9InRydWUiIC8+CiAgPC9zb2RpcG9kaTpuYW1lZHZpZXc+CiAgPG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhNyI+CiAgICA8cmRmOlJERj4KICAgICAgPGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPgogICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PgogICAgICAgIDxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz4KICAgICAgPC9jYzpXb3JrPgogICAgPC9yZGY6UkRGPgogIDwvbWV0YWRhdGE+CiAgPGcKICAgICBpbmtzY2FwZTpsYWJlbD0iTGF5ZXIgMSIKICAgICBpbmtzY2FwZTpncm91cG1vZGU9ImxheWVyIgogICAgIGlkPSJsYXllcjEiPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjEuOTk5OTk5ODg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgIGQ9Ik0gNzIuMTU2OTEsMjUgTCA5NSwyNSIKICAgICAgIGlkPSJwYXRoMzA1OSIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY2MiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MjtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2Utb3BhY2l0eToxIgogICAgICAgZD0iTSAyOS4wNDM0NzgsMjUgTCA1LjA0MzQ3ODEsMjUiCiAgICAgICBpZD0icGF0aDMwNjEiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MztzdHJva2UtbGluZWpvaW46bWl0ZXI7bWFya2VyOm5vbmU7c3Ryb2tlLW9wYWNpdHk6MTt2aXNpYmlsaXR5OnZpc2libGU7ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIgogICAgICAgZD0iTSAyOC45Njg3NSwyLjU5Mzc1IEwgMjguOTY4NzUsNSBMIDI4Ljk2ODc1LDQ1IEwgMjguOTY4NzUsNDcuNDA2MjUgTCAzMS4xMjUsNDYuMzQzNzUgTCA3Mi4xNTYyNSwyNi4zNDM3NSBMIDcyLjE1NjI1LDIzLjY1NjI1IEwgMzEuMTI1LDMuNjU2MjUgTCAyOC45Njg3NSwyLjU5Mzc1IHogTSAzMS45Njg3NSw3LjQwNjI1IEwgNjguMDkzNzUsMjUgTCAzMS45Njg3NSw0Mi41OTM3NSBMIDMxLjk2ODc1LDcuNDA2MjUgeiIKICAgICAgIGlkPSJwYXRoMjYzOCIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY2NjY2NjY2NjY2NjYyIgLz4KICA8L2c+Cjwvc3ZnPgo=' } } +}, { + operation: function(input) { + return input; + } +}); + +joint.shapes.logic.Gate11.define('logic.Not', { + attrs: { image: { 'xlink:href': 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgo8c3ZnCiAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgeG1sbnM6Y2M9Imh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL25zIyIKICAgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIgogICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnNvZGlwb2RpPSJodHRwOi8vc29kaXBvZGkuc291cmNlZm9yZ2UubmV0L0RURC9zb2RpcG9kaS0wLmR0ZCIKICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiCiAgIHdpZHRoPSIxMDAiCiAgIGhlaWdodD0iNTAiCiAgIGlkPSJzdmcyIgogICBzb2RpcG9kaTp2ZXJzaW9uPSIwLjMyIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIwLjQ2IgogICB2ZXJzaW9uPSIxLjAiCiAgIHNvZGlwb2RpOmRvY25hbWU9Ik5PVCBBTlNJLnN2ZyIKICAgaW5rc2NhcGU6b3V0cHV0X2V4dGVuc2lvbj0ib3JnLmlua3NjYXBlLm91dHB1dC5zdmcuaW5rc2NhcGUiPgogIDxkZWZzCiAgICAgaWQ9ImRlZnM0Ij4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiAxNSA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF96PSI1MCA6IDE1IDogMSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIyNSA6IDEwIDogMSIKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI3MTQiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogMC41IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3o9IjEgOiAwLjUgOiAxIgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjAuNSA6IDAuMzMzMzMzMzMgOiAxIgogICAgICAgaWQ9InBlcnNwZWN0aXZlMjgwNiIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlMjgxOSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIzNzIuMDQ3MjQgOiAzNTAuNzg3MzkgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfej0iNzQ0LjA5NDQ4IDogNTI2LjE4MTA5IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA1MjYuMTgxMDkgOiAxIgogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlMjc3NyIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSI3NSA6IDQwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjE1MCA6IDYwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA2MCA6IDEiCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBpZD0icGVyc3BlY3RpdmUzMjc1IgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjUwIDogMzMuMzMzMzMzIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjEwMCA6IDUwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA1MCA6IDEiCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBpZD0icGVyc3BlY3RpdmU1NTMzIgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjMyIDogMjEuMzMzMzMzIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjY0IDogMzIgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfeT0iMCA6IDEwMDAgOiAwIgogICAgICAgaW5rc2NhcGU6dnBfeD0iMCA6IDMyIDogMSIKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI1NTciCiAgICAgICBpbmtzY2FwZTpwZXJzcDNkLW9yaWdpbj0iMjUgOiAxNi42NjY2NjcgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfej0iNTAgOiAyNSA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogMjUgOiAxIgogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIgLz4KICA8L2RlZnM+CiAgPHNvZGlwb2RpOm5hbWVkdmlldwogICAgIGlkPSJiYXNlIgogICAgIHBhZ2Vjb2xvcj0iI2ZmZmZmZiIKICAgICBib3JkZXJjb2xvcj0iIzY2NjY2NiIKICAgICBib3JkZXJvcGFjaXR5PSIxLjAiCiAgICAgaW5rc2NhcGU6cGFnZW9wYWNpdHk9IjAuMCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnpvb209IjgiCiAgICAgaW5rc2NhcGU6Y3g9Ijg0LjY4NTM1MiIKICAgICBpbmtzY2FwZTpjeT0iMTUuMjg4NjI4IgogICAgIGlua3NjYXBlOmRvY3VtZW50LXVuaXRzPSJweCIKICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJsYXllcjEiCiAgICAgc2hvd2dyaWQ9InRydWUiCiAgICAgaW5rc2NhcGU6Z3JpZC1iYm94PSJ0cnVlIgogICAgIGlua3NjYXBlOmdyaWQtcG9pbnRzPSJ0cnVlIgogICAgIGdyaWR0b2xlcmFuY2U9IjEwMDAwIgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTM5OSIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSI4NzQiCiAgICAgaW5rc2NhcGU6d2luZG93LXg9IjMzIgogICAgIGlua3NjYXBlOndpbmRvdy15PSIwIgogICAgIGlua3NjYXBlOnNuYXAtYmJveD0idHJ1ZSI+CiAgICA8aW5rc2NhcGU6Z3JpZAogICAgICAgaWQ9IkdyaWRGcm9tUHJlMDQ2U2V0dGluZ3MiCiAgICAgICB0eXBlPSJ4eWdyaWQiCiAgICAgICBvcmlnaW54PSIwcHgiCiAgICAgICBvcmlnaW55PSIwcHgiCiAgICAgICBzcGFjaW5neD0iMXB4IgogICAgICAgc3BhY2luZ3k9IjFweCIKICAgICAgIGNvbG9yPSIjMDAwMGZmIgogICAgICAgZW1wY29sb3I9IiMwMDAwZmYiCiAgICAgICBvcGFjaXR5PSIwLjIiCiAgICAgICBlbXBvcGFjaXR5PSIwLjQiCiAgICAgICBlbXBzcGFjaW5nPSI1IgogICAgICAgdmlzaWJsZT0idHJ1ZSIKICAgICAgIGVuYWJsZWQ9InRydWUiIC8+CiAgPC9zb2RpcG9kaTpuYW1lZHZpZXc+CiAgPG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhNyI+CiAgICA8cmRmOlJERj4KICAgICAgPGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPgogICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PgogICAgICAgIDxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz4KICAgICAgPC9jYzpXb3JrPgogICAgPC9yZGY6UkRGPgogIDwvbWV0YWRhdGE+CiAgPGcKICAgICBpbmtzY2FwZTpsYWJlbD0iTGF5ZXIgMSIKICAgICBpbmtzY2FwZTpncm91cG1vZGU9ImxheWVyIgogICAgIGlkPSJsYXllcjEiPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjEuOTk5OTk5ODg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgIGQ9Ik0gNzkuMTU2OTEsMjUgTCA5NSwyNSIKICAgICAgIGlkPSJwYXRoMzA1OSIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY2MiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MjtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2Utb3BhY2l0eToxIgogICAgICAgZD0iTSAyOS4wNDM0NzgsMjUgTCA1LjA0MzQ3ODEsMjUiCiAgICAgICBpZD0icGF0aDMwNjEiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MztzdHJva2UtbGluZWpvaW46bWl0ZXI7bWFya2VyOm5vbmU7c3Ryb2tlLW9wYWNpdHk6MTt2aXNpYmlsaXR5OnZpc2libGU7ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIgogICAgICAgZD0iTSAyOC45Njg3NSwyLjU5Mzc1IEwgMjguOTY4NzUsNSBMIDI4Ljk2ODc1LDQ1IEwgMjguOTY4NzUsNDcuNDA2MjUgTCAzMS4xMjUsNDYuMzQzNzUgTCA3Mi4xNTYyNSwyNi4zNDM3NSBMIDcyLjE1NjI1LDIzLjY1NjI1IEwgMzEuMTI1LDMuNjU2MjUgTCAyOC45Njg3NSwyLjU5Mzc1IHogTSAzMS45Njg3NSw3LjQwNjI1IEwgNjguMDkzNzUsMjUgTCAzMS45Njg3NSw0Mi41OTM3NSBMIDMxLjk2ODc1LDcuNDA2MjUgeiIKICAgICAgIGlkPSJwYXRoMjYzOCIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY2NjY2NjY2NjY2NjYyIgLz4KICAgIDxwYXRoCiAgICAgICBzb2RpcG9kaTp0eXBlPSJhcmMiCiAgICAgICBzdHlsZT0iZmlsbDpub25lO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDozO3N0cm9rZS1saW5lam9pbjptaXRlcjttYXJrZXI6bm9uZTtzdHJva2Utb3BhY2l0eToxO3Zpc2liaWxpdHk6dmlzaWJsZTtkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiCiAgICAgICBpZD0icGF0aDI2NzEiCiAgICAgICBzb2RpcG9kaTpjeD0iNzYiCiAgICAgICBzb2RpcG9kaTpjeT0iMjUiCiAgICAgICBzb2RpcG9kaTpyeD0iNCIKICAgICAgIHNvZGlwb2RpOnJ5PSI0IgogICAgICAgZD0iTSA4MCwyNSBBIDQsNCAwIDEgMSA3MiwyNSBBIDQsNCAwIDEgMSA4MCwyNSB6IgogICAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTEsMCkiIC8+CiAgPC9nPgo8L3N2Zz4K' } } +}, { + operation: function(input) { + return !input; + } +}); + +joint.shapes.logic.Gate21.define('logic.Or', { + attrs: { image: { 'xlink:href': 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgo8c3ZnCiAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgeG1sbnM6Y2M9Imh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL25zIyIKICAgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIgogICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnNvZGlwb2RpPSJodHRwOi8vc29kaXBvZGkuc291cmNlZm9yZ2UubmV0L0RURC9zb2RpcG9kaS0wLmR0ZCIKICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiCiAgIHdpZHRoPSIxMDAiCiAgIGhlaWdodD0iNTAiCiAgIGlkPSJzdmcyIgogICBzb2RpcG9kaTp2ZXJzaW9uPSIwLjMyIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIwLjQ2IgogICB2ZXJzaW9uPSIxLjAiCiAgIHNvZGlwb2RpOmRvY25hbWU9Ik9SIEFOU0kuc3ZnIgogICBpbmtzY2FwZTpvdXRwdXRfZXh0ZW5zaW9uPSJvcmcuaW5rc2NhcGUub3V0cHV0LnN2Zy5pbmtzY2FwZSI+CiAgPGRlZnMKICAgICBpZD0iZGVmczQiPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIgogICAgICAgaW5rc2NhcGU6dnBfeD0iMCA6IDE1IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3o9IjUwIDogMTUgOiAxIgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjI1IDogMTAgOiAxIgogICAgICAgaWQ9InBlcnNwZWN0aXZlMjcxNCIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiAwLjUgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfeT0iMCA6IDEwMDAgOiAwIgogICAgICAgaW5rc2NhcGU6dnBfej0iMSA6IDAuNSA6IDEiCiAgICAgICBpbmtzY2FwZTpwZXJzcDNkLW9yaWdpbj0iMC41IDogMC4zMzMzMzMzMyA6IDEiCiAgICAgICBpZD0icGVyc3BlY3RpdmUyODA2IiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBpZD0icGVyc3BlY3RpdmUyODE5IgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjM3Mi4wNDcyNCA6IDM1MC43ODczOSA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF96PSI3NDQuMDk0NDggOiA1MjYuMTgxMDkgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfeT0iMCA6IDEwMDAgOiAwIgogICAgICAgaW5rc2NhcGU6dnBfeD0iMCA6IDUyNi4xODEwOSA6IDEiCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBpZD0icGVyc3BlY3RpdmUyNzc3IgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49Ijc1IDogNDAgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfej0iMTUwIDogNjAgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfeT0iMCA6IDEwMDAgOiAwIgogICAgICAgaW5rc2NhcGU6dnBfeD0iMCA6IDYwIDogMSIKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTMyNzUiCiAgICAgICBpbmtzY2FwZTpwZXJzcDNkLW9yaWdpbj0iNTAgOiAzMy4zMzMzMzMgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfej0iMTAwIDogNTAgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfeT0iMCA6IDEwMDAgOiAwIgogICAgICAgaW5rc2NhcGU6dnBfeD0iMCA6IDUwIDogMSIKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTU1MzMiCiAgICAgICBpbmtzY2FwZTpwZXJzcDNkLW9yaWdpbj0iMzIgOiAyMS4zMzMzMzMgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfej0iNjQgOiAzMiA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogMzIgOiAxIgogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlMjU1NyIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIyNSA6IDE2LjY2NjY2NyA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF96PSI1MCA6IDI1IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiAyNSA6IDEiCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIiAvPgogIDwvZGVmcz4KICA8c29kaXBvZGk6bmFtZWR2aWV3CiAgICAgaWQ9ImJhc2UiCiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIgogICAgIGJvcmRlcmNvbG9yPSIjNjY2NjY2IgogICAgIGJvcmRlcm9wYWNpdHk9IjEuMCIKICAgICBpbmtzY2FwZTpwYWdlb3BhY2l0eT0iMC4wIgogICAgIGlua3NjYXBlOnBhZ2VzaGFkb3c9IjIiCiAgICAgaW5rc2NhcGU6em9vbT0iNCIKICAgICBpbmtzY2FwZTpjeD0iMTEzLjAwMDM5IgogICAgIGlua3NjYXBlOmN5PSIxMi44OTM3MzEiCiAgICAgaW5rc2NhcGU6ZG9jdW1lbnQtdW5pdHM9InB4IgogICAgIGlua3NjYXBlOmN1cnJlbnQtbGF5ZXI9ImcyNTYwIgogICAgIHNob3dncmlkPSJmYWxzZSIKICAgICBpbmtzY2FwZTpncmlkLWJib3g9InRydWUiCiAgICAgaW5rc2NhcGU6Z3JpZC1wb2ludHM9InRydWUiCiAgICAgZ3JpZHRvbGVyYW5jZT0iMTAwMDAiCiAgICAgaW5rc2NhcGU6d2luZG93LXdpZHRoPSIxMzk5IgogICAgIGlua3NjYXBlOndpbmRvdy1oZWlnaHQ9Ijg3NCIKICAgICBpbmtzY2FwZTp3aW5kb3cteD0iMzciCiAgICAgaW5rc2NhcGU6d2luZG93LXk9Ii00IgogICAgIGlua3NjYXBlOnNuYXAtYmJveD0idHJ1ZSI+CiAgICA8aW5rc2NhcGU6Z3JpZAogICAgICAgaWQ9IkdyaWRGcm9tUHJlMDQ2U2V0dGluZ3MiCiAgICAgICB0eXBlPSJ4eWdyaWQiCiAgICAgICBvcmlnaW54PSIwcHgiCiAgICAgICBvcmlnaW55PSIwcHgiCiAgICAgICBzcGFjaW5neD0iMXB4IgogICAgICAgc3BhY2luZ3k9IjFweCIKICAgICAgIGNvbG9yPSIjMDAwMGZmIgogICAgICAgZW1wY29sb3I9IiMwMDAwZmYiCiAgICAgICBvcGFjaXR5PSIwLjIiCiAgICAgICBlbXBvcGFjaXR5PSIwLjQiCiAgICAgICBlbXBzcGFjaW5nPSI1IgogICAgICAgdmlzaWJsZT0idHJ1ZSIKICAgICAgIGVuYWJsZWQ9InRydWUiIC8+CiAgPC9zb2RpcG9kaTpuYW1lZHZpZXc+CiAgPG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhNyI+CiAgICA8cmRmOlJERj4KICAgICAgPGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPgogICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PgogICAgICAgIDxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz4KICAgICAgPC9jYzpXb3JrPgogICAgPC9yZGY6UkRGPgogIDwvbWV0YWRhdGE+CiAgPGcKICAgICBpbmtzY2FwZTpsYWJlbD0iTGF5ZXIgMSIKICAgICBpbmtzY2FwZTpncm91cG1vZGU9ImxheWVyIgogICAgIGlkPSJsYXllcjEiPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjI7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgIGQ9Im0gNzAsMjUgYyAyMCwwIDI1LDAgMjUsMCIKICAgICAgIGlkPSJwYXRoMzA1OSIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY2MiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MjtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2Utb3BhY2l0eToxIgogICAgICAgZD0iTSAzMSwxNSA1LDE1IgogICAgICAgaWQ9InBhdGgzMDYxIiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjEuOTk5OTk5ODg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgIGQ9Ik0gMzIsMzUgNSwzNSIKICAgICAgIGlkPSJwYXRoMzk0NCIgLz4KICAgIDxnCiAgICAgICBpZD0iZzI1NjAiCiAgICAgICBpbmtzY2FwZTpsYWJlbD0iTGF5ZXIgMSIKICAgICAgIHRyYW5zZm9ybT0idHJhbnNsYXRlKDI2LjUsLTM5LjUpIj4KICAgICAgPHBhdGgKICAgICAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6ZXZlbm9kZDtzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MztzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2Utb3BhY2l0eToxIgogICAgICAgICBkPSJNIC0yLjQwNjI1LDQ0LjUgTCAtMC40MDYyNSw0Ni45Mzc1IEMgLTAuNDA2MjUsNDYuOTM3NSA1LjI1LDUzLjkzNzU0OSA1LjI1LDY0LjUgQyA1LjI1LDc1LjA2MjQ1MSAtMC40MDYyNSw4Mi4wNjI1IC0wLjQwNjI1LDgyLjA2MjUgTCAtMi40MDYyNSw4NC41IEwgMC43NSw4NC41IEwgMTQuNzUsODQuNSBDIDE3LjE1ODA3Niw4NC41MDAwMDEgMjIuNDM5Njk5LDg0LjUyNDUxNCAyOC4zNzUsODIuMDkzNzUgQyAzNC4zMTAzMDEsNzkuNjYyOTg2IDQwLjkxMTUzNiw3NC43NTA0ODQgNDYuMDYyNSw2NS4yMTg3NSBMIDQ0Ljc1LDY0LjUgTCA0Ni4wNjI1LDYzLjc4MTI1IEMgMzUuNzU5Mzg3LDQ0LjcxNTU5IDE5LjUwNjU3NCw0NC41IDE0Ljc1LDQ0LjUgTCAwLjc1LDQ0LjUgTCAtMi40MDYyNSw0NC41IHogTSAzLjQ2ODc1LDQ3LjUgTCAxNC43NSw0Ny41IEMgMTkuNDM0MTczLDQ3LjUgMzMuMDM2ODUsNDcuMzY5NzkzIDQyLjcxODc1LDY0LjUgQyAzNy45NTE5NjQsNzIuOTI5MDc1IDMyLjE5NzQ2OSw3Ny4xODM5MSAyNyw3OS4zMTI1IEMgMjEuNjM5MzM5LDgxLjUwNzkyNCAxNy4xNTgwNzUsODEuNTAwMDAxIDE0Ljc1LDgxLjUgTCAzLjUsODEuNSBDIDUuMzczNTg4NCw3OC4zOTE1NjYgOC4yNSw3Mi40NTA2NSA4LjI1LDY0LjUgQyA4LjI1LDU2LjUyNjY0NiA1LjM0MTQ2ODYsNTAuNTk5ODE1IDMuNDY4NzUsNDcuNSB6IgogICAgICAgICBpZD0icGF0aDQ5NzMiCiAgICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY2NzY2NjY3NjY2NjY2NjY2NzY2NzYyIgLz4KICAgIDwvZz4KICA8L2c+Cjwvc3ZnPgo=' } } +}, { + operation: function(input1, input2) { + return input1 || input2; + } +}); + +joint.shapes.logic.Gate21.define('logic.And', { + attrs: { image: { 'xlink:href': 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgo8c3ZnCiAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgeG1sbnM6Y2M9Imh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL25zIyIKICAgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIgogICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnNvZGlwb2RpPSJodHRwOi8vc29kaXBvZGkuc291cmNlZm9yZ2UubmV0L0RURC9zb2RpcG9kaS0wLmR0ZCIKICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiCiAgIHdpZHRoPSIxMDAiCiAgIGhlaWdodD0iNTAiCiAgIGlkPSJzdmcyIgogICBzb2RpcG9kaTp2ZXJzaW9uPSIwLjMyIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIwLjQ2IgogICB2ZXJzaW9uPSIxLjAiCiAgIHNvZGlwb2RpOmRvY25hbWU9IkFORCBBTlNJLnN2ZyIKICAgaW5rc2NhcGU6b3V0cHV0X2V4dGVuc2lvbj0ib3JnLmlua3NjYXBlLm91dHB1dC5zdmcuaW5rc2NhcGUiPgogIDxkZWZzCiAgICAgaWQ9ImRlZnM0Ij4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiAxNSA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF96PSI1MCA6IDE1IDogMSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIyNSA6IDEwIDogMSIKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI3MTQiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogMC41IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3o9IjEgOiAwLjUgOiAxIgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjAuNSA6IDAuMzMzMzMzMzMgOiAxIgogICAgICAgaWQ9InBlcnNwZWN0aXZlMjgwNiIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlMjgxOSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIzNzIuMDQ3MjQgOiAzNTAuNzg3MzkgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfej0iNzQ0LjA5NDQ4IDogNTI2LjE4MTA5IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA1MjYuMTgxMDkgOiAxIgogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlMjc3NyIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSI3NSA6IDQwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjE1MCA6IDYwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA2MCA6IDEiCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBpZD0icGVyc3BlY3RpdmUzMjc1IgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjUwIDogMzMuMzMzMzMzIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjEwMCA6IDUwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA1MCA6IDEiCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBpZD0icGVyc3BlY3RpdmU1NTMzIgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjMyIDogMjEuMzMzMzMzIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjY0IDogMzIgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfeT0iMCA6IDEwMDAgOiAwIgogICAgICAgaW5rc2NhcGU6dnBfeD0iMCA6IDMyIDogMSIKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiIC8+CiAgPC9kZWZzPgogIDxzb2RpcG9kaTpuYW1lZHZpZXcKICAgICBpZD0iYmFzZSIKICAgICBwYWdlY29sb3I9IiNmZmZmZmYiCiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiCiAgICAgYm9yZGVyb3BhY2l0eT0iMS4wIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwLjAiCiAgICAgaW5rc2NhcGU6cGFnZXNoYWRvdz0iMiIKICAgICBpbmtzY2FwZTp6b29tPSI4IgogICAgIGlua3NjYXBlOmN4PSI1Ni42OTgzNDgiCiAgICAgaW5rc2NhcGU6Y3k9IjI1LjMyNjg5OSIKICAgICBpbmtzY2FwZTpkb2N1bWVudC11bml0cz0icHgiCiAgICAgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0ibGF5ZXIxIgogICAgIHNob3dncmlkPSJ0cnVlIgogICAgIGlua3NjYXBlOmdyaWQtYmJveD0idHJ1ZSIKICAgICBpbmtzY2FwZTpncmlkLXBvaW50cz0idHJ1ZSIKICAgICBncmlkdG9sZXJhbmNlPSIxMDAwMCIKICAgICBpbmtzY2FwZTp3aW5kb3ctd2lkdGg9IjEzOTkiCiAgICAgaW5rc2NhcGU6d2luZG93LWhlaWdodD0iODc0IgogICAgIGlua3NjYXBlOndpbmRvdy14PSIzMyIKICAgICBpbmtzY2FwZTp3aW5kb3cteT0iMCIKICAgICBpbmtzY2FwZTpzbmFwLWJib3g9InRydWUiPgogICAgPGlua3NjYXBlOmdyaWQKICAgICAgIGlkPSJHcmlkRnJvbVByZTA0NlNldHRpbmdzIgogICAgICAgdHlwZT0ieHlncmlkIgogICAgICAgb3JpZ2lueD0iMHB4IgogICAgICAgb3JpZ2lueT0iMHB4IgogICAgICAgc3BhY2luZ3g9IjFweCIKICAgICAgIHNwYWNpbmd5PSIxcHgiCiAgICAgICBjb2xvcj0iIzAwMDBmZiIKICAgICAgIGVtcGNvbG9yPSIjMDAwMGZmIgogICAgICAgb3BhY2l0eT0iMC4yIgogICAgICAgZW1wb3BhY2l0eT0iMC40IgogICAgICAgZW1wc3BhY2luZz0iNSIKICAgICAgIHZpc2libGU9InRydWUiCiAgICAgICBlbmFibGVkPSJ0cnVlIiAvPgogIDwvc29kaXBvZGk6bmFtZWR2aWV3PgogIDxtZXRhZGF0YQogICAgIGlkPSJtZXRhZGF0YTciPgogICAgPHJkZjpSREY+CiAgICAgIDxjYzpXb3JrCiAgICAgICAgIHJkZjphYm91dD0iIj4KICAgICAgICA8ZGM6Zm9ybWF0PmltYWdlL3N2Zyt4bWw8L2RjOmZvcm1hdD4KICAgICAgICA8ZGM6dHlwZQogICAgICAgICAgIHJkZjpyZXNvdXJjZT0iaHR0cDovL3B1cmwub3JnL2RjL2RjbWl0eXBlL1N0aWxsSW1hZ2UiIC8+CiAgICAgIDwvY2M6V29yaz4KICAgIDwvcmRmOlJERj4KICA8L21ldGFkYXRhPgogIDxnCiAgICAgaW5rc2NhcGU6bGFiZWw9IkxheWVyIDEiCiAgICAgaW5rc2NhcGU6Z3JvdXBtb2RlPSJsYXllciIKICAgICBpZD0ibGF5ZXIxIj4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICBkPSJtIDcwLDI1IGMgMjAsMCAyNSwwIDI1LDAiCiAgICAgICBpZD0icGF0aDMwNTkiCiAgICAgICBzb2RpcG9kaTpub2RldHlwZXM9ImNjIiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjI7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgIGQ9Ik0gMzEsMTUgNSwxNSIKICAgICAgIGlkPSJwYXRoMzA2MSIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxLjk5OTk5OTg4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICBkPSJNIDMyLDM1IDUsMzUiCiAgICAgICBpZD0icGF0aDM5NDQiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImZvbnQtc2l6ZTptZWRpdW07Zm9udC1zdHlsZTpub3JtYWw7Zm9udC12YXJpYW50Om5vcm1hbDtmb250LXdlaWdodDpub3JtYWw7Zm9udC1zdHJldGNoOm5vcm1hbDt0ZXh0LWluZGVudDowO3RleHQtYWxpZ246c3RhcnQ7dGV4dC1kZWNvcmF0aW9uOm5vbmU7bGluZS1oZWlnaHQ6bm9ybWFsO2xldHRlci1zcGFjaW5nOm5vcm1hbDt3b3JkLXNwYWNpbmc6bm9ybWFsO3RleHQtdHJhbnNmb3JtOm5vbmU7ZGlyZWN0aW9uOmx0cjtibG9jay1wcm9ncmVzc2lvbjp0Yjt3cml0aW5nLW1vZGU6bHItdGI7dGV4dC1hbmNob3I6c3RhcnQ7ZmlsbDojMDAwMDAwO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTpub25lO3N0cm9rZS13aWR0aDozO21hcmtlcjpub25lO3Zpc2liaWxpdHk6dmlzaWJsZTtkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGU7Zm9udC1mYW1pbHk6Qml0c3RyZWFtIFZlcmEgU2FuczstaW5rc2NhcGUtZm9udC1zcGVjaWZpY2F0aW9uOkJpdHN0cmVhbSBWZXJhIFNhbnMiCiAgICAgICBkPSJNIDMwLDUgTCAzMCw2LjQyODU3MTQgTCAzMCw0My41NzE0MjkgTCAzMCw0NSBMIDMxLjQyODU3MSw0NSBMIDUwLjQ3NjE5LDQ1IEMgNjEuNzQ0MDk4LDQ1IDcwLjQ3NjE5LDM1Ljk5OTk1NSA3MC40NzYxOSwyNSBDIDcwLjQ3NjE5LDE0LjAwMDA0NSA2MS43NDQwOTksNS4wMDAwMDAyIDUwLjQ3NjE5LDUgQyA1MC40NzYxOSw1IDUwLjQ3NjE5LDUgMzEuNDI4NTcxLDUgTCAzMCw1IHogTSAzMi44NTcxNDMsNy44NTcxNDI5IEMgNDAuODM0MjY0LDcuODU3MTQyOSA0NS45MTgzNjgsNy44NTcxNDI5IDQ4LjA5NTIzOCw3Ljg1NzE0MjkgQyA0OS4yODU3MTQsNy44NTcxNDI5IDQ5Ljg4MDk1Miw3Ljg1NzE0MjkgNTAuMTc4NTcxLDcuODU3MTQyOSBDIDUwLjMyNzM4MSw3Ljg1NzE0MjkgNTAuNDA5MjI3LDcuODU3MTQyOSA1MC40NDY0MjksNy44NTcxNDI5IEMgNTAuNDY1MDI5LDcuODU3MTQyOSA1MC40NzE1NDMsNy44NTcxNDI5IDUwLjQ3NjE5LDcuODU3MTQyOSBDIDYwLjIzNjg1Myw3Ljg1NzE0MyA2Ny4xNDI4NTcsMTUuNDk3MDk4IDY3LjE0Mjg1NywyNSBDIDY3LjE0Mjg1NywzNC41MDI5MDIgNTkuNzYwNjYyLDQyLjE0Mjg1NyA1MCw0Mi4xNDI4NTcgTCAzMi44NTcxNDMsNDIuMTQyODU3IEwgMzIuODU3MTQzLDcuODU3MTQyOSB6IgogICAgICAgaWQ9InBhdGgyODg0IgogICAgICAgc29kaXBvZGk6bm9kZXR5cGVzPSJjY2NjY2NzY2NjY3Nzc3NzY2NjIiAvPgogIDwvZz4KPC9zdmc+Cg==' } } + +}, { + operation: function(input1, input2) { + return input1 && input2; + } +}); + +joint.shapes.logic.Gate21.define('logic.Nor', { + attrs: { image: { 'xlink:href': 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgo8c3ZnCiAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgeG1sbnM6Y2M9Imh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL25zIyIKICAgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIgogICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnNvZGlwb2RpPSJodHRwOi8vc29kaXBvZGkuc291cmNlZm9yZ2UubmV0L0RURC9zb2RpcG9kaS0wLmR0ZCIKICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiCiAgIHdpZHRoPSIxMDAiCiAgIGhlaWdodD0iNTAiCiAgIGlkPSJzdmcyIgogICBzb2RpcG9kaTp2ZXJzaW9uPSIwLjMyIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIwLjQ2IgogICB2ZXJzaW9uPSIxLjAiCiAgIHNvZGlwb2RpOmRvY25hbWU9Ik5PUiBBTlNJLnN2ZyIKICAgaW5rc2NhcGU6b3V0cHV0X2V4dGVuc2lvbj0ib3JnLmlua3NjYXBlLm91dHB1dC5zdmcuaW5rc2NhcGUiPgogIDxkZWZzCiAgICAgaWQ9ImRlZnM0Ij4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiAxNSA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF96PSI1MCA6IDE1IDogMSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIyNSA6IDEwIDogMSIKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI3MTQiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogMC41IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3o9IjEgOiAwLjUgOiAxIgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjAuNSA6IDAuMzMzMzMzMzMgOiAxIgogICAgICAgaWQ9InBlcnNwZWN0aXZlMjgwNiIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlMjgxOSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIzNzIuMDQ3MjQgOiAzNTAuNzg3MzkgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfej0iNzQ0LjA5NDQ4IDogNTI2LjE4MTA5IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA1MjYuMTgxMDkgOiAxIgogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlMjc3NyIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSI3NSA6IDQwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjE1MCA6IDYwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA2MCA6IDEiCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBpZD0icGVyc3BlY3RpdmUzMjc1IgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjUwIDogMzMuMzMzMzMzIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjEwMCA6IDUwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA1MCA6IDEiCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBpZD0icGVyc3BlY3RpdmU1NTMzIgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjMyIDogMjEuMzMzMzMzIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjY0IDogMzIgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfeT0iMCA6IDEwMDAgOiAwIgogICAgICAgaW5rc2NhcGU6dnBfeD0iMCA6IDMyIDogMSIKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI1NTciCiAgICAgICBpbmtzY2FwZTpwZXJzcDNkLW9yaWdpbj0iMjUgOiAxNi42NjY2NjcgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfej0iNTAgOiAyNSA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogMjUgOiAxIgogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIgLz4KICA8L2RlZnM+CiAgPHNvZGlwb2RpOm5hbWVkdmlldwogICAgIGlkPSJiYXNlIgogICAgIHBhZ2Vjb2xvcj0iI2ZmZmZmZiIKICAgICBib3JkZXJjb2xvcj0iIzY2NjY2NiIKICAgICBib3JkZXJvcGFjaXR5PSIxLjAiCiAgICAgaW5rc2NhcGU6cGFnZW9wYWNpdHk9IjAuMCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnpvb209IjEiCiAgICAgaW5rc2NhcGU6Y3g9Ijc4LjY3NzY0NCIKICAgICBpbmtzY2FwZTpjeT0iMjIuMTAyMzQ0IgogICAgIGlua3NjYXBlOmRvY3VtZW50LXVuaXRzPSJweCIKICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJsYXllcjEiCiAgICAgc2hvd2dyaWQ9InRydWUiCiAgICAgaW5rc2NhcGU6Z3JpZC1iYm94PSJ0cnVlIgogICAgIGlua3NjYXBlOmdyaWQtcG9pbnRzPSJ0cnVlIgogICAgIGdyaWR0b2xlcmFuY2U9IjEwMDAwIgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTM5OSIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSI4NzQiCiAgICAgaW5rc2NhcGU6d2luZG93LXg9IjM3IgogICAgIGlua3NjYXBlOndpbmRvdy15PSItNCIKICAgICBpbmtzY2FwZTpzbmFwLWJib3g9InRydWUiPgogICAgPGlua3NjYXBlOmdyaWQKICAgICAgIGlkPSJHcmlkRnJvbVByZTA0NlNldHRpbmdzIgogICAgICAgdHlwZT0ieHlncmlkIgogICAgICAgb3JpZ2lueD0iMHB4IgogICAgICAgb3JpZ2lueT0iMHB4IgogICAgICAgc3BhY2luZ3g9IjFweCIKICAgICAgIHNwYWNpbmd5PSIxcHgiCiAgICAgICBjb2xvcj0iIzAwMDBmZiIKICAgICAgIGVtcGNvbG9yPSIjMDAwMGZmIgogICAgICAgb3BhY2l0eT0iMC4yIgogICAgICAgZW1wb3BhY2l0eT0iMC40IgogICAgICAgZW1wc3BhY2luZz0iNSIKICAgICAgIHZpc2libGU9InRydWUiCiAgICAgICBlbmFibGVkPSJ0cnVlIiAvPgogIDwvc29kaXBvZGk6bmFtZWR2aWV3PgogIDxtZXRhZGF0YQogICAgIGlkPSJtZXRhZGF0YTciPgogICAgPHJkZjpSREY+CiAgICAgIDxjYzpXb3JrCiAgICAgICAgIHJkZjphYm91dD0iIj4KICAgICAgICA8ZGM6Zm9ybWF0PmltYWdlL3N2Zyt4bWw8L2RjOmZvcm1hdD4KICAgICAgICA8ZGM6dHlwZQogICAgICAgICAgIHJkZjpyZXNvdXJjZT0iaHR0cDovL3B1cmwub3JnL2RjL2RjbWl0eXBlL1N0aWxsSW1hZ2UiIC8+CiAgICAgIDwvY2M6V29yaz4KICAgIDwvcmRmOlJERj4KICA8L21ldGFkYXRhPgogIDxnCiAgICAgaW5rc2NhcGU6bGFiZWw9IkxheWVyIDEiCiAgICAgaW5rc2NhcGU6Z3JvdXBtb2RlPSJsYXllciIKICAgICBpZD0ibGF5ZXIxIj4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICBkPSJNIDc5LDI1IEMgOTksMjUgOTUsMjUgOTUsMjUiCiAgICAgICBpZD0icGF0aDMwNTkiCiAgICAgICBzb2RpcG9kaTpub2RldHlwZXM9ImNjIiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjI7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgIGQ9Ik0gMzEsMTUgNSwxNSIKICAgICAgIGlkPSJwYXRoMzA2MSIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxLjk5OTk5OTg4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICBkPSJNIDMyLDM1IDUsMzUiCiAgICAgICBpZD0icGF0aDM5NDQiIC8+CiAgICA8ZwogICAgICAgaWQ9ImcyNTYwIgogICAgICAgaW5rc2NhcGU6bGFiZWw9IkxheWVyIDEiCiAgICAgICB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyNi41LC0zOS41KSI+CiAgICAgIDxwYXRoCiAgICAgICAgIHN0eWxlPSJmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjM7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgICAgZD0iTSAtMi40MDYyNSw0NC41IEwgLTAuNDA2MjUsNDYuOTM3NSBDIC0wLjQwNjI1LDQ2LjkzNzUgNS4yNSw1My45Mzc1NDkgNS4yNSw2NC41IEMgNS4yNSw3NS4wNjI0NTEgLTAuNDA2MjUsODIuMDYyNSAtMC40MDYyNSw4Mi4wNjI1IEwgLTIuNDA2MjUsODQuNSBMIDAuNzUsODQuNSBMIDE0Ljc1LDg0LjUgQyAxNy4xNTgwNzYsODQuNTAwMDAxIDIyLjQzOTY5OSw4NC41MjQ1MTQgMjguMzc1LDgyLjA5Mzc1IEMgMzQuMzEwMzAxLDc5LjY2Mjk4NiA0MC45MTE1MzYsNzQuNzUwNDg0IDQ2LjA2MjUsNjUuMjE4NzUgTCA0NC43NSw2NC41IEwgNDYuMDYyNSw2My43ODEyNSBDIDM1Ljc1OTM4Nyw0NC43MTU1OSAxOS41MDY1NzQsNDQuNSAxNC43NSw0NC41IEwgMC43NSw0NC41IEwgLTIuNDA2MjUsNDQuNSB6IE0gMy40Njg3NSw0Ny41IEwgMTQuNzUsNDcuNSBDIDE5LjQzNDE3Myw0Ny41IDMzLjAzNjg1LDQ3LjM2OTc5MyA0Mi43MTg3NSw2NC41IEMgMzcuOTUxOTY0LDcyLjkyOTA3NSAzMi4xOTc0NjksNzcuMTgzOTEgMjcsNzkuMzEyNSBDIDIxLjYzOTMzOSw4MS41MDc5MjQgMTcuMTU4MDc1LDgxLjUwMDAwMSAxNC43NSw4MS41IEwgMy41LDgxLjUgQyA1LjM3MzU4ODQsNzguMzkxNTY2IDguMjUsNzIuNDUwNjUgOC4yNSw2NC41IEMgOC4yNSw1Ni41MjY2NDYgNS4zNDE0Njg2LDUwLjU5OTgxNSAzLjQ2ODc1LDQ3LjUgeiIKICAgICAgICAgaWQ9InBhdGg0OTczIgogICAgICAgICBzb2RpcG9kaTpub2RldHlwZXM9ImNjc2NjY2NzY2NjY2NjY2Njc2Njc2MiIC8+CiAgICAgIDxwYXRoCiAgICAgICAgIHNvZGlwb2RpOnR5cGU9ImFyYyIKICAgICAgICAgc3R5bGU9ImZpbGw6bm9uZTtmaWxsLW9wYWNpdHk6MTtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MztzdHJva2UtbGluZWpvaW46bWl0ZXI7bWFya2VyOm5vbmU7c3Ryb2tlLW9wYWNpdHk6MTt2aXNpYmlsaXR5OnZpc2libGU7ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlIgogICAgICAgICBpZD0icGF0aDI2MDQiCiAgICAgICAgIHNvZGlwb2RpOmN4PSI3NSIKICAgICAgICAgc29kaXBvZGk6Y3k9IjI1IgogICAgICAgICBzb2RpcG9kaTpyeD0iNCIKICAgICAgICAgc29kaXBvZGk6cnk9IjQiCiAgICAgICAgIGQ9Ik0gNzksMjUgQSA0LDQgMCAxIDEgNzEsMjUgQSA0LDQgMCAxIDEgNzksMjUgeiIKICAgICAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTI2LjUsMzkuNSkiIC8+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K' } } +}, { + operation: function(input1, input2) { + return !(input1 || input2); + } +}); + +joint.shapes.logic.Gate21.define('logic.Nand', { + attrs: { image: { 'xlink:href': 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgo8c3ZnCiAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgeG1sbnM6Y2M9Imh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL25zIyIKICAgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIgogICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnNvZGlwb2RpPSJodHRwOi8vc29kaXBvZGkuc291cmNlZm9yZ2UubmV0L0RURC9zb2RpcG9kaS0wLmR0ZCIKICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiCiAgIHdpZHRoPSIxMDAiCiAgIGhlaWdodD0iNTAiCiAgIGlkPSJzdmcyIgogICBzb2RpcG9kaTp2ZXJzaW9uPSIwLjMyIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIwLjQ2IgogICB2ZXJzaW9uPSIxLjAiCiAgIHNvZGlwb2RpOmRvY25hbWU9Ik5BTkQgQU5TSS5zdmciCiAgIGlua3NjYXBlOm91dHB1dF9leHRlbnNpb249Im9yZy5pbmtzY2FwZS5vdXRwdXQuc3ZnLmlua3NjYXBlIj4KICA8ZGVmcwogICAgIGlkPSJkZWZzNCI+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogMTUgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfeT0iMCA6IDEwMDAgOiAwIgogICAgICAgaW5rc2NhcGU6dnBfej0iNTAgOiAxNSA6IDEiCiAgICAgICBpbmtzY2FwZTpwZXJzcDNkLW9yaWdpbj0iMjUgOiAxMCA6IDEiCiAgICAgICBpZD0icGVyc3BlY3RpdmUyNzE0IiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIgogICAgICAgaW5rc2NhcGU6dnBfeD0iMCA6IDAuNSA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF96PSIxIDogMC41IDogMSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIwLjUgOiAwLjMzMzMzMzMzIDogMSIKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI4MDYiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI4MTkiCiAgICAgICBpbmtzY2FwZTpwZXJzcDNkLW9yaWdpbj0iMzcyLjA0NzI0IDogMzUwLjc4NzM5IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9Ijc0NC4wOTQ0OCA6IDUyNi4xODEwOSA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogNTI2LjE4MTA5IDogMSIKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI3NzciCiAgICAgICBpbmtzY2FwZTpwZXJzcDNkLW9yaWdpbj0iNzUgOiA0MCA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF96PSIxNTAgOiA2MCA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogNjAgOiAxIgogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlMzI3NSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSI1MCA6IDMzLjMzMzMzMyA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF96PSIxMDAgOiA1MCA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogNTAgOiAxIgogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlNTUzMyIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIzMiA6IDIxLjMzMzMzMyA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF96PSI2NCA6IDMyIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiAzMiA6IDEiCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIiAvPgogIDwvZGVmcz4KICA8c29kaXBvZGk6bmFtZWR2aWV3CiAgICAgaWQ9ImJhc2UiCiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIgogICAgIGJvcmRlcmNvbG9yPSIjNjY2NjY2IgogICAgIGJvcmRlcm9wYWNpdHk9IjEuMCIKICAgICBpbmtzY2FwZTpwYWdlb3BhY2l0eT0iMC4wIgogICAgIGlua3NjYXBlOnBhZ2VzaGFkb3c9IjIiCiAgICAgaW5rc2NhcGU6em9vbT0iMTYiCiAgICAgaW5rc2NhcGU6Y3g9Ijc4LjI4MzMwNyIKICAgICBpbmtzY2FwZTpjeT0iMTYuNDQyODQzIgogICAgIGlua3NjYXBlOmRvY3VtZW50LXVuaXRzPSJweCIKICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJsYXllcjEiCiAgICAgc2hvd2dyaWQ9InRydWUiCiAgICAgaW5rc2NhcGU6Z3JpZC1iYm94PSJ0cnVlIgogICAgIGlua3NjYXBlOmdyaWQtcG9pbnRzPSJ0cnVlIgogICAgIGdyaWR0b2xlcmFuY2U9IjEwMDAwIgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTM5OSIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSI4NzQiCiAgICAgaW5rc2NhcGU6d2luZG93LXg9IjMzIgogICAgIGlua3NjYXBlOndpbmRvdy15PSIwIgogICAgIGlua3NjYXBlOnNuYXAtYmJveD0idHJ1ZSI+CiAgICA8aW5rc2NhcGU6Z3JpZAogICAgICAgaWQ9IkdyaWRGcm9tUHJlMDQ2U2V0dGluZ3MiCiAgICAgICB0eXBlPSJ4eWdyaWQiCiAgICAgICBvcmlnaW54PSIwcHgiCiAgICAgICBvcmlnaW55PSIwcHgiCiAgICAgICBzcGFjaW5neD0iMXB4IgogICAgICAgc3BhY2luZ3k9IjFweCIKICAgICAgIGNvbG9yPSIjMDAwMGZmIgogICAgICAgZW1wY29sb3I9IiMwMDAwZmYiCiAgICAgICBvcGFjaXR5PSIwLjIiCiAgICAgICBlbXBvcGFjaXR5PSIwLjQiCiAgICAgICBlbXBzcGFjaW5nPSI1IgogICAgICAgdmlzaWJsZT0idHJ1ZSIKICAgICAgIGVuYWJsZWQ9InRydWUiIC8+CiAgPC9zb2RpcG9kaTpuYW1lZHZpZXc+CiAgPG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhNyI+CiAgICA8cmRmOlJERj4KICAgICAgPGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPgogICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PgogICAgICAgIDxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz4KICAgICAgPC9jYzpXb3JrPgogICAgPC9yZGY6UkRGPgogIDwvbWV0YWRhdGE+CiAgPGcKICAgICBpbmtzY2FwZTpsYWJlbD0iTGF5ZXIgMSIKICAgICBpbmtzY2FwZTpncm91cG1vZGU9ImxheWVyIgogICAgIGlkPSJsYXllcjEiPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjI7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgIGQ9Ik0gNzksMjUgQyA5MS44LDI1IDk1LDI1IDk1LDI1IgogICAgICAgaWQ9InBhdGgzMDU5IgogICAgICAgc29kaXBvZGk6bm9kZXR5cGVzPSJjYyIgLz4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICBkPSJNIDMxLDE1IDUsMTUiCiAgICAgICBpZD0icGF0aDMwNjEiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MS45OTk5OTk4ODtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2Utb3BhY2l0eToxIgogICAgICAgZD0iTSAzMiwzNSA1LDM1IgogICAgICAgaWQ9InBhdGgzOTQ0IiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJmb250LXNpemU6bWVkaXVtO2ZvbnQtc3R5bGU6bm9ybWFsO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7dGV4dC1pbmRlbnQ6MDt0ZXh0LWFsaWduOnN0YXJ0O3RleHQtZGVjb3JhdGlvbjpub25lO2xpbmUtaGVpZ2h0Om5vcm1hbDtsZXR0ZXItc3BhY2luZzpub3JtYWw7d29yZC1zcGFjaW5nOm5vcm1hbDt0ZXh0LXRyYW5zZm9ybTpub25lO2RpcmVjdGlvbjpsdHI7YmxvY2stcHJvZ3Jlc3Npb246dGI7d3JpdGluZy1tb2RlOmxyLXRiO3RleHQtYW5jaG9yOnN0YXJ0O2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MzttYXJrZXI6bm9uZTt2aXNpYmlsaXR5OnZpc2libGU7ZGlzcGxheTppbmxpbmU7b3ZlcmZsb3c6dmlzaWJsZTtlbmFibGUtYmFja2dyb3VuZDphY2N1bXVsYXRlO2ZvbnQtZmFtaWx5OkJpdHN0cmVhbSBWZXJhIFNhbnM7LWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjpCaXRzdHJlYW0gVmVyYSBTYW5zIgogICAgICAgZD0iTSAzMCw1IEwgMzAsNi40Mjg1NzE0IEwgMzAsNDMuNTcxNDI5IEwgMzAsNDUgTCAzMS40Mjg1NzEsNDUgTCA1MC40NzYxOSw0NSBDIDYxLjc0NDA5OCw0NSA3MC40NzYxOSwzNS45OTk5NTUgNzAuNDc2MTksMjUgQyA3MC40NzYxOSwxNC4wMDAwNDUgNjEuNzQ0MDk5LDUuMDAwMDAwMiA1MC40NzYxOSw1IEMgNTAuNDc2MTksNSA1MC40NzYxOSw1IDMxLjQyODU3MSw1IEwgMzAsNSB6IE0gMzIuODU3MTQzLDcuODU3MTQyOSBDIDQwLjgzNDI2NCw3Ljg1NzE0MjkgNDUuOTE4MzY4LDcuODU3MTQyOSA0OC4wOTUyMzgsNy44NTcxNDI5IEMgNDkuMjg1NzE0LDcuODU3MTQyOSA0OS44ODA5NTIsNy44NTcxNDI5IDUwLjE3ODU3MSw3Ljg1NzE0MjkgQyA1MC4zMjczODEsNy44NTcxNDI5IDUwLjQwOTIyNyw3Ljg1NzE0MjkgNTAuNDQ2NDI5LDcuODU3MTQyOSBDIDUwLjQ2NTAyOSw3Ljg1NzE0MjkgNTAuNDcxNTQzLDcuODU3MTQyOSA1MC40NzYxOSw3Ljg1NzE0MjkgQyA2MC4yMzY4NTMsNy44NTcxNDMgNjcuMTQyODU3LDE1LjQ5NzA5OCA2Ny4xNDI4NTcsMjUgQyA2Ny4xNDI4NTcsMzQuNTAyOTAyIDU5Ljc2MDY2Miw0Mi4xNDI4NTcgNTAsNDIuMTQyODU3IEwgMzIuODU3MTQzLDQyLjE0Mjg1NyBMIDMyLjg1NzE0Myw3Ljg1NzE0MjkgeiIKICAgICAgIGlkPSJwYXRoMjg4NCIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY2NjY2Njc2NjY2Nzc3Nzc2NjYyIgLz4KICAgIDxwYXRoCiAgICAgICBzb2RpcG9kaTp0eXBlPSJhcmMiCiAgICAgICBzdHlsZT0iZmlsbDpub25lO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDozO3N0cm9rZS1saW5lam9pbjptaXRlcjttYXJrZXI6bm9uZTtzdHJva2Utb3BhY2l0eToxO3Zpc2liaWxpdHk6dmlzaWJsZTtkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiCiAgICAgICBpZD0icGF0aDQwMDgiCiAgICAgICBzb2RpcG9kaTpjeD0iNzUiCiAgICAgICBzb2RpcG9kaTpjeT0iMjUiCiAgICAgICBzb2RpcG9kaTpyeD0iNCIKICAgICAgIHNvZGlwb2RpOnJ5PSI0IgogICAgICAgZD0iTSA3OSwyNSBBIDQsNCAwIDEgMSA3MSwyNSBBIDQsNCAwIDEgMSA3OSwyNSB6IiAvPgogIDwvZz4KPC9zdmc+Cg==' } } +}, { + operation: function(input1, input2) { + return !(input1 && input2); + } +}); + +joint.shapes.logic.Gate21.define('logic.Xor', { + attrs: { image: { 'xlink:href': 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgo8c3ZnCiAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgeG1sbnM6Y2M9Imh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL25zIyIKICAgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIgogICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnNvZGlwb2RpPSJodHRwOi8vc29kaXBvZGkuc291cmNlZm9yZ2UubmV0L0RURC9zb2RpcG9kaS0wLmR0ZCIKICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiCiAgIHdpZHRoPSIxMDAiCiAgIGhlaWdodD0iNTAiCiAgIGlkPSJzdmcyIgogICBzb2RpcG9kaTp2ZXJzaW9uPSIwLjMyIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIwLjQ2IgogICB2ZXJzaW9uPSIxLjAiCiAgIHNvZGlwb2RpOmRvY25hbWU9IlhPUiBBTlNJLnN2ZyIKICAgaW5rc2NhcGU6b3V0cHV0X2V4dGVuc2lvbj0ib3JnLmlua3NjYXBlLm91dHB1dC5zdmcuaW5rc2NhcGUiPgogIDxkZWZzCiAgICAgaWQ9ImRlZnM0Ij4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiAxNSA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF96PSI1MCA6IDE1IDogMSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIyNSA6IDEwIDogMSIKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI3MTQiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogMC41IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3o9IjEgOiAwLjUgOiAxIgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjAuNSA6IDAuMzMzMzMzMzMgOiAxIgogICAgICAgaWQ9InBlcnNwZWN0aXZlMjgwNiIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlMjgxOSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIzNzIuMDQ3MjQgOiAzNTAuNzg3MzkgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfej0iNzQ0LjA5NDQ4IDogNTI2LjE4MTA5IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA1MjYuMTgxMDkgOiAxIgogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlMjc3NyIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSI3NSA6IDQwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjE1MCA6IDYwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA2MCA6IDEiCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBpZD0icGVyc3BlY3RpdmUzMjc1IgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjUwIDogMzMuMzMzMzMzIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjEwMCA6IDUwIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiA1MCA6IDEiCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBpZD0icGVyc3BlY3RpdmU1NTMzIgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjMyIDogMjEuMzMzMzMzIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjY0IDogMzIgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfeT0iMCA6IDEwMDAgOiAwIgogICAgICAgaW5rc2NhcGU6dnBfeD0iMCA6IDMyIDogMSIKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI1NTciCiAgICAgICBpbmtzY2FwZTpwZXJzcDNkLW9yaWdpbj0iMjUgOiAxNi42NjY2NjcgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfej0iNTAgOiAyNSA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogMjUgOiAxIgogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIgLz4KICA8L2RlZnM+CiAgPHNvZGlwb2RpOm5hbWVkdmlldwogICAgIGlkPSJiYXNlIgogICAgIHBhZ2Vjb2xvcj0iI2ZmZmZmZiIKICAgICBib3JkZXJjb2xvcj0iIzY2NjY2NiIKICAgICBib3JkZXJvcGFjaXR5PSIxLjAiCiAgICAgaW5rc2NhcGU6cGFnZW9wYWNpdHk9IjAuMCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnpvb209IjUuNjU2ODU0MiIKICAgICBpbmtzY2FwZTpjeD0iMjUuOTM4MTE2IgogICAgIGlua3NjYXBlOmN5PSIxNy4yMzAwNSIKICAgICBpbmtzY2FwZTpkb2N1bWVudC11bml0cz0icHgiCiAgICAgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0ibGF5ZXIxIgogICAgIHNob3dncmlkPSJ0cnVlIgogICAgIGlua3NjYXBlOmdyaWQtYmJveD0idHJ1ZSIKICAgICBpbmtzY2FwZTpncmlkLXBvaW50cz0idHJ1ZSIKICAgICBncmlkdG9sZXJhbmNlPSIxMDAwMCIKICAgICBpbmtzY2FwZTp3aW5kb3ctd2lkdGg9IjEzOTkiCiAgICAgaW5rc2NhcGU6d2luZG93LWhlaWdodD0iODc0IgogICAgIGlua3NjYXBlOndpbmRvdy14PSIzMyIKICAgICBpbmtzY2FwZTp3aW5kb3cteT0iMCIKICAgICBpbmtzY2FwZTpzbmFwLWJib3g9InRydWUiPgogICAgPGlua3NjYXBlOmdyaWQKICAgICAgIGlkPSJHcmlkRnJvbVByZTA0NlNldHRpbmdzIgogICAgICAgdHlwZT0ieHlncmlkIgogICAgICAgb3JpZ2lueD0iMHB4IgogICAgICAgb3JpZ2lueT0iMHB4IgogICAgICAgc3BhY2luZ3g9IjFweCIKICAgICAgIHNwYWNpbmd5PSIxcHgiCiAgICAgICBjb2xvcj0iIzAwMDBmZiIKICAgICAgIGVtcGNvbG9yPSIjMDAwMGZmIgogICAgICAgb3BhY2l0eT0iMC4yIgogICAgICAgZW1wb3BhY2l0eT0iMC40IgogICAgICAgZW1wc3BhY2luZz0iNSIKICAgICAgIHZpc2libGU9InRydWUiCiAgICAgICBlbmFibGVkPSJ0cnVlIiAvPgogIDwvc29kaXBvZGk6bmFtZWR2aWV3PgogIDxtZXRhZGF0YQogICAgIGlkPSJtZXRhZGF0YTciPgogICAgPHJkZjpSREY+CiAgICAgIDxjYzpXb3JrCiAgICAgICAgIHJkZjphYm91dD0iIj4KICAgICAgICA8ZGM6Zm9ybWF0PmltYWdlL3N2Zyt4bWw8L2RjOmZvcm1hdD4KICAgICAgICA8ZGM6dHlwZQogICAgICAgICAgIHJkZjpyZXNvdXJjZT0iaHR0cDovL3B1cmwub3JnL2RjL2RjbWl0eXBlL1N0aWxsSW1hZ2UiIC8+CiAgICAgIDwvY2M6V29yaz4KICAgIDwvcmRmOlJERj4KICA8L21ldGFkYXRhPgogIDxnCiAgICAgaW5rc2NhcGU6bGFiZWw9IkxheWVyIDEiCiAgICAgaW5rc2NhcGU6Z3JvdXBtb2RlPSJsYXllciIKICAgICBpZD0ibGF5ZXIxIj4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICBkPSJtIDcwLDI1IGMgMjAsMCAyNSwwIDI1LDAiCiAgICAgICBpZD0icGF0aDMwNTkiCiAgICAgICBzb2RpcG9kaTpub2RldHlwZXM9ImNjIiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjEuOTk5OTk5ODg7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgIGQ9Ik0gMzAuMzg1NzE3LDE1IEwgNC45OTk5OTk4LDE1IgogICAgICAgaWQ9InBhdGgzMDYxIiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJmaWxsOm5vbmU7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjEuOTk5OTk5NzY7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgIGQ9Ik0gMzEuMzYyMDkxLDM1IEwgNC45OTk5OTk4LDM1IgogICAgICAgaWQ9InBhdGgzOTQ0IiAvPgogICAgPGcKICAgICAgIGlkPSJnMjU2MCIKICAgICAgIGlua3NjYXBlOmxhYmVsPSJMYXllciAxIgogICAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjYuNSwtMzkuNSkiPgogICAgICA8cGF0aAogICAgICAgICBpZD0icGF0aDM1MTYiCiAgICAgICAgIHN0eWxlPSJmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjM7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgICAgZD0iTSAtMi4yNSw4MS41MDAwMDUgQyAtMy44NDczNzQsODQuMTQ0NDA1IC00LjUsODQuNTAwMDA1IC00LjUsODQuNTAwMDA1IEwgLTguMTU2MjUsODQuNTAwMDA1IEwgLTYuMTU2MjUsODIuMDYyNTA1IEMgLTYuMTU2MjUsODIuMDYyNTA1IC0wLjUsNzUuMDYyNDUxIC0wLjUsNjQuNSBDIC0wLjUsNTMuOTM3NTQ5IC02LjE1NjI1LDQ2LjkzNzUgLTYuMTU2MjUsNDYuOTM3NSBMIC04LjE1NjI1LDQ0LjUgTCAtNC41LDQ0LjUgQyAtMy43MTg3NSw0NS40Mzc1IC0zLjA3ODEyNSw0Ni4xNTYyNSAtMi4yODEyNSw0Ny41IEMgLTAuNDA4NTMxLDUwLjU5OTgxNSAyLjUsNTYuNTI2NjQ2IDIuNSw2NC41IEMgMi41LDcyLjQ1MDY1IC0wLjM5NjY5Nyw3OC4zNzk0MjUgLTIuMjUsODEuNTAwMDA1IHoiCiAgICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY2NjY3NjY2Njc2MiIC8+CiAgICAgIDxwYXRoCiAgICAgICAgIHN0eWxlPSJmaWxsOiMwMDAwMDA7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOmV2ZW5vZGQ7c3Ryb2tlOm5vbmU7c3Ryb2tlLXdpZHRoOjM7c3Ryb2tlLWxpbmVjYXA6YnV0dDtzdHJva2UtbGluZWpvaW46bWl0ZXI7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgICAgZD0iTSAtMi40MDYyNSw0NC41IEwgLTAuNDA2MjUsNDYuOTM3NSBDIC0wLjQwNjI1LDQ2LjkzNzUgNS4yNSw1My45Mzc1NDkgNS4yNSw2NC41IEMgNS4yNSw3NS4wNjI0NTEgLTAuNDA2MjUsODIuMDYyNSAtMC40MDYyNSw4Mi4wNjI1IEwgLTIuNDA2MjUsODQuNSBMIDAuNzUsODQuNSBMIDE0Ljc1LDg0LjUgQyAxNy4xNTgwNzYsODQuNTAwMDAxIDIyLjQzOTY5OSw4NC41MjQ1MTQgMjguMzc1LDgyLjA5Mzc1IEMgMzQuMzEwMzAxLDc5LjY2Mjk4NiA0MC45MTE1MzYsNzQuNzUwNDg0IDQ2LjA2MjUsNjUuMjE4NzUgTCA0NC43NSw2NC41IEwgNDYuMDYyNSw2My43ODEyNSBDIDM1Ljc1OTM4Nyw0NC43MTU1OSAxOS41MDY1NzQsNDQuNSAxNC43NSw0NC41IEwgMC43NSw0NC41IEwgLTIuNDA2MjUsNDQuNSB6IE0gMy40Njg3NSw0Ny41IEwgMTQuNzUsNDcuNSBDIDE5LjQzNDE3Myw0Ny41IDMzLjAzNjg1LDQ3LjM2OTc5MyA0Mi43MTg3NSw2NC41IEMgMzcuOTUxOTY0LDcyLjkyOTA3NSAzMi4xOTc0NjksNzcuMTgzOTEgMjcsNzkuMzEyNSBDIDIxLjYzOTMzOSw4MS41MDc5MjQgMTcuMTU4MDc1LDgxLjUwMDAwMSAxNC43NSw4MS41IEwgMy41LDgxLjUgQyA1LjM3MzU4ODQsNzguMzkxNTY2IDguMjUsNzIuNDUwNjUgOC4yNSw2NC41IEMgOC4yNSw1Ni41MjY2NDYgNS4zNDE0Njg2LDUwLjU5OTgxNSAzLjQ2ODc1LDQ3LjUgeiIKICAgICAgICAgaWQ9InBhdGg0OTczIgogICAgICAgICBzb2RpcG9kaTpub2RldHlwZXM9ImNjc2NjY2NzY2NjY2NjY2Njc2Njc2MiIC8+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K' } } +}, { + operation: function(input1, input2) { + return (!input1 || input2) && (input1 || !input2); + } +}); + +joint.shapes.logic.Gate21.define('logic.Xnor', { + attrs: { image: { 'xlink:href': 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgo8c3ZnCiAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgeG1sbnM6Y2M9Imh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL25zIyIKICAgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIgogICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnNvZGlwb2RpPSJodHRwOi8vc29kaXBvZGkuc291cmNlZm9yZ2UubmV0L0RURC9zb2RpcG9kaS0wLmR0ZCIKICAgeG1sbnM6aW5rc2NhcGU9Imh0dHA6Ly93d3cuaW5rc2NhcGUub3JnL25hbWVzcGFjZXMvaW5rc2NhcGUiCiAgIHdpZHRoPSIxMDAiCiAgIGhlaWdodD0iNTAiCiAgIGlkPSJzdmcyIgogICBzb2RpcG9kaTp2ZXJzaW9uPSIwLjMyIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIwLjQ2IgogICB2ZXJzaW9uPSIxLjAiCiAgIHNvZGlwb2RpOmRvY25hbWU9IlhOT1IgQU5TSS5zdmciCiAgIGlua3NjYXBlOm91dHB1dF9leHRlbnNpb249Im9yZy5pbmtzY2FwZS5vdXRwdXQuc3ZnLmlua3NjYXBlIj4KICA8ZGVmcwogICAgIGlkPSJkZWZzNCI+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogMTUgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfeT0iMCA6IDEwMDAgOiAwIgogICAgICAgaW5rc2NhcGU6dnBfej0iNTAgOiAxNSA6IDEiCiAgICAgICBpbmtzY2FwZTpwZXJzcDNkLW9yaWdpbj0iMjUgOiAxMCA6IDEiCiAgICAgICBpZD0icGVyc3BlY3RpdmUyNzE0IiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIgogICAgICAgaW5rc2NhcGU6dnBfeD0iMCA6IDAuNSA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF96PSIxIDogMC41IDogMSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIwLjUgOiAwLjMzMzMzMzMzIDogMSIKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI4MDYiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI4MTkiCiAgICAgICBpbmtzY2FwZTpwZXJzcDNkLW9yaWdpbj0iMzcyLjA0NzI0IDogMzUwLjc4NzM5IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9Ijc0NC4wOTQ0OCA6IDUyNi4xODEwOSA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogNTI2LjE4MTA5IDogMSIKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiIC8+CiAgICA8aW5rc2NhcGU6cGVyc3BlY3RpdmUKICAgICAgIGlkPSJwZXJzcGVjdGl2ZTI3NzciCiAgICAgICBpbmtzY2FwZTpwZXJzcDNkLW9yaWdpbj0iNzUgOiA0MCA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF96PSIxNTAgOiA2MCA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogNjAgOiAxIgogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlMzI3NSIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSI1MCA6IDMzLjMzMzMzMyA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF96PSIxMDAgOiA1MCA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiCiAgICAgICBpbmtzY2FwZTp2cF94PSIwIDogNTAgOiAxIgogICAgICAgc29kaXBvZGk6dHlwZT0iaW5rc2NhcGU6cGVyc3AzZCIgLz4KICAgIDxpbmtzY2FwZTpwZXJzcGVjdGl2ZQogICAgICAgaWQ9InBlcnNwZWN0aXZlNTUzMyIKICAgICAgIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIzMiA6IDIxLjMzMzMzMyA6IDEiCiAgICAgICBpbmtzY2FwZTp2cF96PSI2NCA6IDMyIDogMSIKICAgICAgIGlua3NjYXBlOnZwX3k9IjAgOiAxMDAwIDogMCIKICAgICAgIGlua3NjYXBlOnZwX3g9IjAgOiAzMiA6IDEiCiAgICAgICBzb2RpcG9kaTp0eXBlPSJpbmtzY2FwZTpwZXJzcDNkIiAvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlCiAgICAgICBpZD0icGVyc3BlY3RpdmUyNTU3IgogICAgICAgaW5rc2NhcGU6cGVyc3AzZC1vcmlnaW49IjI1IDogMTYuNjY2NjY3IDogMSIKICAgICAgIGlua3NjYXBlOnZwX3o9IjUwIDogMjUgOiAxIgogICAgICAgaW5rc2NhcGU6dnBfeT0iMCA6IDEwMDAgOiAwIgogICAgICAgaW5rc2NhcGU6dnBfeD0iMCA6IDI1IDogMSIKICAgICAgIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiIC8+CiAgPC9kZWZzPgogIDxzb2RpcG9kaTpuYW1lZHZpZXcKICAgICBpZD0iYmFzZSIKICAgICBwYWdlY29sb3I9IiNmZmZmZmYiCiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiCiAgICAgYm9yZGVyb3BhY2l0eT0iMS4wIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwLjAiCiAgICAgaW5rc2NhcGU6cGFnZXNoYWRvdz0iMiIKICAgICBpbmtzY2FwZTp6b29tPSI0IgogICAgIGlua3NjYXBlOmN4PSI5NS43MjM2NiIKICAgICBpbmtzY2FwZTpjeT0iLTI2Ljc3NTAyMyIKICAgICBpbmtzY2FwZTpkb2N1bWVudC11bml0cz0icHgiCiAgICAgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0ibGF5ZXIxIgogICAgIHNob3dncmlkPSJ0cnVlIgogICAgIGlua3NjYXBlOmdyaWQtYmJveD0idHJ1ZSIKICAgICBpbmtzY2FwZTpncmlkLXBvaW50cz0idHJ1ZSIKICAgICBncmlkdG9sZXJhbmNlPSIxMDAwMCIKICAgICBpbmtzY2FwZTp3aW5kb3ctd2lkdGg9IjEzOTkiCiAgICAgaW5rc2NhcGU6d2luZG93LWhlaWdodD0iODc0IgogICAgIGlua3NjYXBlOndpbmRvdy14PSIzMyIKICAgICBpbmtzY2FwZTp3aW5kb3cteT0iMCIKICAgICBpbmtzY2FwZTpzbmFwLWJib3g9InRydWUiPgogICAgPGlua3NjYXBlOmdyaWQKICAgICAgIGlkPSJHcmlkRnJvbVByZTA0NlNldHRpbmdzIgogICAgICAgdHlwZT0ieHlncmlkIgogICAgICAgb3JpZ2lueD0iMHB4IgogICAgICAgb3JpZ2lueT0iMHB4IgogICAgICAgc3BhY2luZ3g9IjFweCIKICAgICAgIHNwYWNpbmd5PSIxcHgiCiAgICAgICBjb2xvcj0iIzAwMDBmZiIKICAgICAgIGVtcGNvbG9yPSIjMDAwMGZmIgogICAgICAgb3BhY2l0eT0iMC4yIgogICAgICAgZW1wb3BhY2l0eT0iMC40IgogICAgICAgZW1wc3BhY2luZz0iNSIKICAgICAgIHZpc2libGU9InRydWUiCiAgICAgICBlbmFibGVkPSJ0cnVlIiAvPgogIDwvc29kaXBvZGk6bmFtZWR2aWV3PgogIDxtZXRhZGF0YQogICAgIGlkPSJtZXRhZGF0YTciPgogICAgPHJkZjpSREY+CiAgICAgIDxjYzpXb3JrCiAgICAgICAgIHJkZjphYm91dD0iIj4KICAgICAgICA8ZGM6Zm9ybWF0PmltYWdlL3N2Zyt4bWw8L2RjOmZvcm1hdD4KICAgICAgICA8ZGM6dHlwZQogICAgICAgICAgIHJkZjpyZXNvdXJjZT0iaHR0cDovL3B1cmwub3JnL2RjL2RjbWl0eXBlL1N0aWxsSW1hZ2UiIC8+CiAgICAgIDwvY2M6V29yaz4KICAgIDwvcmRmOlJERj4KICA8L21ldGFkYXRhPgogIDxnCiAgICAgaW5rc2NhcGU6bGFiZWw9IkxheWVyIDEiCiAgICAgaW5rc2NhcGU6Z3JvdXBtb2RlPSJsYXllciIKICAgICBpZD0ibGF5ZXIxIj4KICAgIDxwYXRoCiAgICAgICBzdHlsZT0iZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyLjAwMDAwMDI0O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICBkPSJNIDc4LjMzMzMzMiwyNSBDIDkxLjY2NjY2NiwyNSA5NSwyNSA5NSwyNSIKICAgICAgIGlkPSJwYXRoMzA1OSIKICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY2MiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MS45OTk5OTk4ODtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2Utb3BhY2l0eToxIgogICAgICAgZD0iTSAzMC4zODU3MTcsMTUgTCA0Ljk5OTk5OTgsMTUiCiAgICAgICBpZD0icGF0aDMwNjEiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MS45OTk5OTk3NjtzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2Utb3BhY2l0eToxIgogICAgICAgZD0iTSAzMS4zNjIwOTEsMzUgTCA0Ljk5OTk5OTgsMzUiCiAgICAgICBpZD0icGF0aDM5NDQiIC8+CiAgICA8ZwogICAgICAgaWQ9ImcyNTYwIgogICAgICAgaW5rc2NhcGU6bGFiZWw9IkxheWVyIDEiCiAgICAgICB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyNi41LC0zOS41KSI+CiAgICAgIDxwYXRoCiAgICAgICAgIGlkPSJwYXRoMzUxNiIKICAgICAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6ZXZlbm9kZDtzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MztzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2Utb3BhY2l0eToxIgogICAgICAgICBkPSJNIC0yLjI1LDgxLjUwMDAwNSBDIC0zLjg0NzM3NCw4NC4xNDQ0MDUgLTQuNSw4NC41MDAwMDUgLTQuNSw4NC41MDAwMDUgTCAtOC4xNTYyNSw4NC41MDAwMDUgTCAtNi4xNTYyNSw4Mi4wNjI1MDUgQyAtNi4xNTYyNSw4Mi4wNjI1MDUgLTAuNSw3NS4wNjI0NTEgLTAuNSw2NC41IEMgLTAuNSw1My45Mzc1NDkgLTYuMTU2MjUsNDYuOTM3NSAtNi4xNTYyNSw0Ni45Mzc1IEwgLTguMTU2MjUsNDQuNSBMIC00LjUsNDQuNSBDIC0zLjcxODc1LDQ1LjQzNzUgLTMuMDc4MTI1LDQ2LjE1NjI1IC0yLjI4MTI1LDQ3LjUgQyAtMC40MDg1MzEsNTAuNTk5ODE1IDIuNSw1Ni41MjY2NDYgMi41LDY0LjUgQyAyLjUsNzIuNDUwNjUgLTAuMzk2Njk3LDc4LjM3OTQyNSAtMi4yNSw4MS41MDAwMDUgeiIKICAgICAgICAgc29kaXBvZGk6bm9kZXR5cGVzPSJjY2Njc2NjY2NzYyIgLz4KICAgICAgPHBhdGgKICAgICAgICAgc3R5bGU9ImZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6ZXZlbm9kZDtzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MztzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2Utb3BhY2l0eToxIgogICAgICAgICBkPSJNIC0yLjQwNjI1LDQ0LjUgTCAtMC40MDYyNSw0Ni45Mzc1IEMgLTAuNDA2MjUsNDYuOTM3NSA1LjI1LDUzLjkzNzU0OSA1LjI1LDY0LjUgQyA1LjI1LDc1LjA2MjQ1MSAtMC40MDYyNSw4Mi4wNjI1IC0wLjQwNjI1LDgyLjA2MjUgTCAtMi40MDYyNSw4NC41IEwgMC43NSw4NC41IEwgMTQuNzUsODQuNSBDIDE3LjE1ODA3Niw4NC41MDAwMDEgMjIuNDM5Njk5LDg0LjUyNDUxNCAyOC4zNzUsODIuMDkzNzUgQyAzNC4zMTAzMDEsNzkuNjYyOTg2IDQwLjkxMTUzNiw3NC43NTA0ODQgNDYuMDYyNSw2NS4yMTg3NSBMIDQ0Ljc1LDY0LjUgTCA0Ni4wNjI1LDYzLjc4MTI1IEMgMzUuNzU5Mzg3LDQ0LjcxNTU5IDE5LjUwNjU3NCw0NC41IDE0Ljc1LDQ0LjUgTCAwLjc1LDQ0LjUgTCAtMi40MDYyNSw0NC41IHogTSAzLjQ2ODc1LDQ3LjUgTCAxNC43NSw0Ny41IEMgMTkuNDM0MTczLDQ3LjUgMzMuMDM2ODUsNDcuMzY5NzkzIDQyLjcxODc1LDY0LjUgQyAzNy45NTE5NjQsNzIuOTI5MDc1IDMyLjE5NzQ2OSw3Ny4xODM5MSAyNyw3OS4zMTI1IEMgMjEuNjM5MzM5LDgxLjUwNzkyNCAxNy4xNTgwNzUsODEuNTAwMDAxIDE0Ljc1LDgxLjUgTCAzLjUsODEuNSBDIDUuMzczNTg4NCw3OC4zOTE1NjYgOC4yNSw3Mi40NTA2NSA4LjI1LDY0LjUgQyA4LjI1LDU2LjUyNjY0NiA1LjM0MTQ2ODYsNTAuNTk5ODE1IDMuNDY4NzUsNDcuNSB6IgogICAgICAgICBpZD0icGF0aDQ5NzMiCiAgICAgICAgIHNvZGlwb2RpOm5vZGV0eXBlcz0iY2NzY2NjY3NjY2NjY2NjY2NzY2NzYyIgLz4KICAgIDwvZz4KICAgIDxwYXRoCiAgICAgICBzb2RpcG9kaTp0eXBlPSJhcmMiCiAgICAgICBzdHlsZT0iZmlsbDpub25lO2ZpbGwtb3BhY2l0eToxO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDozO3N0cm9rZS1saW5lam9pbjptaXRlcjttYXJrZXI6bm9uZTtzdHJva2Utb3BhY2l0eToxO3Zpc2liaWxpdHk6dmlzaWJsZTtkaXNwbGF5OmlubGluZTtvdmVyZmxvdzp2aXNpYmxlO2VuYWJsZS1iYWNrZ3JvdW5kOmFjY3VtdWxhdGUiCiAgICAgICBpZD0icGF0aDM1NTEiCiAgICAgICBzb2RpcG9kaTpjeD0iNzUiCiAgICAgICBzb2RpcG9kaTpjeT0iMjUiCiAgICAgICBzb2RpcG9kaTpyeD0iNCIKICAgICAgIHNvZGlwb2RpOnJ5PSI0IgogICAgICAgZD0iTSA3OSwyNSBBIDQsNCAwIDEgMSA3MSwyNSBBIDQsNCAwIDEgMSA3OSwyNSB6IiAvPgogIDwvZz4KPC9zdmc+Cg==' } } +}, { + operation: function(input1, input2) { + return (!input1 || !input2) && (input1 || input2); + } +}); + +joint.dia.Link.define('logic.Wire', { + attrs: { + '.connection': { 'stroke-width': 2 }, + '.marker-vertex': { r: 7 } + }, + + router: { name: 'orthogonal' }, + connector: { name: 'rounded', args: { radius: 10 } } +}, { + arrowheadMarkup: [ + '', + '', + '' + ].join(''), + + vertexMarkup: [ + '', + '', + '', + '', + '', + 'Remove vertex.', + '', + '', + '' + ].join('') +}); + +if (typeof exports === 'object') { + + var graphlib = require('graphlib'); + var dagre = require('dagre'); +} + +// In the browser, these variables are set to undefined because of JavaScript hoisting. +// In that case, should grab them from the window object. +graphlib = graphlib || (typeof window !== 'undefined' && window.graphlib); +dagre = dagre || (typeof window !== 'undefined' && window.dagre); + +joint.layout.DirectedGraph = { + + exportElement: function(element) { + + // The width and height of the element. + return element.size(); + }, + + exportLink: function(link) { + + var labelSize = link.get('labelSize') || {}; + var edge = { + // The number of ranks to keep between the source and target of the edge. + minLen: link.get('minLen') || 1, + // The weight to assign edges. Higher weight edges are generally + // made shorter and straighter than lower weight edges. + weight: link.get('weight') || 1, + // Where to place the label relative to the edge. + // l = left, c = center r = right. + labelpos: link.get('labelPosition') || 'c', + // How many pixels to move the label away from the edge. + // Applies only when labelpos is l or r. + labeloffset: link.get('labelOffset') || 0, + // The width of the edge label in pixels. + width: labelSize.width || 0, + // The height of the edge label in pixels. + height: labelSize.height || 0 + }; + + return edge; + }, + + importElement: function(opt, v, gl) { + + var element = this.getCell(v); + var glNode = gl.node(v); + + if (opt.setPosition) { + opt.setPosition(element, glNode); + } else { + element.set('position', { + x: glNode.x - glNode.width / 2, + y: glNode.y - glNode.height / 2 + }); + } + }, + + importLink: function(opt, edgeObj, gl) { + + var link = this.getCell(edgeObj.name); + var glEdge = gl.edge(edgeObj); + var points = glEdge.points || []; + + // check the `setLinkVertices` here for backwards compatibility + if (opt.setVertices || opt.setLinkVertices) { + if (joint.util.isFunction(opt.setVertices)) { + opt.setVertices(link, points); + } else { + // Remove the first and last point from points array. + // Those are source/target element connection points + // ie. they lies on the edge of connected elements. + link.set('vertices', points.slice(1, points.length - 1)); + } + } + + if (opt.setLabels && ('x' in glEdge) && ('y' in glEdge)) { + var labelPosition = { x: glEdge.x, y: glEdge.y}; + if (joint.util.isFunction(opt.setLabels)) { + opt.setLabels(link, labelPosition, points); + } else { + // Convert the absolute label position to a relative position + // towards the closest point on the edge + var polyline = g.Polyline(points); + var length = polyline.closestPointLength(labelPosition); + var closestPoint = polyline.pointAtLength(length); + var distance = length / polyline.length(); + link.label(0, { + position: { + distance: distance, + offset: g.Point(labelPosition).difference(closestPoint).toJSON() + } + }); + } + } + }, + + layout: function(graphOrCells, opt) { + + var graph; + + if (graphOrCells instanceof joint.dia.Graph) { + graph = graphOrCells; + } else { + // Reset cells in dry mode so the graph reference is not stored on the cells. + // `sort: false` to prevent elements to change their order based on the z-index + graph = (new joint.dia.Graph()).resetCells(graphOrCells, { dry: true, sort: false }); + } + + // This is not needed anymore. + graphOrCells = null; + + opt = joint.util.defaults(opt || {}, { + resizeClusters: true, + clusterPadding: 10, + exportElement: this.exportElement, + exportLink: this.exportLink + }); + + // create a graphlib.Graph that represents the joint.dia.Graph + var glGraph = graph.toGraphLib({ + directed: true, + // We are about to use edge naming feature. + multigraph: true, + // We are able to layout graphs with embeds. + compound: true, + setNodeLabel: opt.exportElement, + setEdgeLabel: opt.exportLink, + setEdgeName: function(link) { + // Graphlib edges have no ids. We use edge name property + // to store and retrieve ids instead. + return link.id; + } + }); + + var glLabel = {}; + var marginX = opt.marginX || 0; + var marginY = opt.marginY || 0; + + // Dagre layout accepts options as lower case. + // Direction for rank nodes. Can be TB, BT, LR, or RL + if (opt.rankDir) glLabel.rankdir = opt.rankDir; + // Alignment for rank nodes. Can be UL, UR, DL, or DR + if (opt.align) glLabel.align = opt.align; + // Number of pixels that separate nodes horizontally in the layout. + if (opt.nodeSep) glLabel.nodesep = opt.nodeSep; + // Number of pixels that separate edges horizontally in the layout. + if (opt.edgeSep) glLabel.edgesep = opt.edgeSep; + // Number of pixels between each rank in the layout. + if (opt.rankSep) glLabel.ranksep = opt.rankSep; + // Type of algorithm to assign a rank to each node in the input graph. + // Possible values: network-simplex, tight-tree or longest-path + if (opt.ranker) glLabel.ranker = opt.ranker; + // Number of pixels to use as a margin around the left and right of the graph. + if (marginX) glLabel.marginx = marginX; + // Number of pixels to use as a margin around the top and bottom of the graph. + if (marginY) glLabel.marginy = marginY; + + // Set the option object for the graph label. + glGraph.setGraph(glLabel); + + // Executes the layout. + dagre.layout(glGraph, { debugTiming: !!opt.debugTiming }); + + // Wrap all graph changes into a batch. + graph.startBatch('layout'); + + // Update the graph. + graph.fromGraphLib(glGraph, { + importNode: this.importElement.bind(graph, opt), + importEdge: this.importLink.bind(graph, opt) + }); + + if (opt.resizeClusters) { + // Resize and reposition cluster elements (parents of other elements) + // to fit their children. + // 1. filter clusters only + // 2. map id on cells + // 3. sort cells by their depth (the deepest first) + // 4. resize cell to fit their direct children only. + var clusters = glGraph.nodes() + .filter(function(v) { return glGraph.children(v).length > 0; }) + .map(graph.getCell.bind(graph)) + .sort(function(aCluster, bCluster) { + return bCluster.getAncestors().length - aCluster.getAncestors().length; + }); + + joint.util.invoke(clusters, 'fitEmbeds', { padding: opt.clusterPadding }); + } + + graph.stopBatch('layout'); + + // Width and height of the graph extended by margins. + var glSize = glGraph.graph(); + // Return the bounding box of the graph after the layout. + return g.Rect( + marginX, + marginY, + Math.abs(glSize.width - 2 * marginX), + Math.abs(glSize.height - 2 * marginY) + ); + }, + + fromGraphLib: function(glGraph, opt) { + + opt = opt || {}; + + var importNode = opt.importNode || joint.util.noop; + var importEdge = opt.importEdge || joint.util.noop; + var graph = (this instanceof joint.dia.Graph) ? this : new joint.dia.Graph; + + // Import all nodes. + glGraph.nodes().forEach(function(node) { + importNode.call(graph, node, glGraph, graph, opt); + }); + + // Import all edges. + glGraph.edges().forEach(function(edge) { + importEdge.call(graph, edge, glGraph, graph, opt); + }); + + return graph; + }, + + // Create new graphlib graph from existing JointJS graph. + toGraphLib: function(graph, opt) { + + opt = opt || {}; + + var glGraphType = joint.util.pick(opt, 'directed', 'compound', 'multigraph'); + var glGraph = new graphlib.Graph(glGraphType); + var setNodeLabel = opt.setNodeLabel || joint.util.noop; + var setEdgeLabel = opt.setEdgeLabel || joint.util.noop; + var setEdgeName = opt.setEdgeName || joint.util.noop; + var collection = graph.get('cells'); + + for (var i = 0, n = collection.length; i < n; i++) { + + var cell = collection.at(i); + if (cell.isLink()) { + + var source = cell.get('source'); + var target = cell.get('target'); + + // Links that end at a point are ignored. + if (!source.id || !target.id) break; + + // Note that if we are creating a multigraph we can name the edges. If + // we try to name edges on a non-multigraph an exception is thrown. + glGraph.setEdge(source.id, target.id, setEdgeLabel(cell), setEdgeName(cell)); + + } else { + + glGraph.setNode(cell.id, setNodeLabel(cell)); + + // For the compound graphs we have to take embeds into account. + if (glGraph.isCompound() && cell.has('parent')) { + var parentId = cell.get('parent'); + if (collection.has(parentId)) { + // Make sure the parent cell is included in the graph (this can + // happen when the layout is run on part of the graph only). + glGraph.setParent(cell.id, parentId); + } + } + } + } + + return glGraph; + } +}; + +joint.dia.Graph.prototype.toGraphLib = function(opt) { + + return joint.layout.DirectedGraph.toGraphLib(this, opt); +}; + +joint.dia.Graph.prototype.fromGraphLib = function(glGraph, opt) { + + return joint.layout.DirectedGraph.fromGraphLib.call(this, glGraph, opt); +}; + + + joint.g = g; + joint.V = joint.Vectorizer = V; + + return joint; + +})); diff --git a/www/js/jquery-ui.min.js b/www/js/jquery-ui.min.js new file mode 100644 index 0000000..25398a1 --- /dev/null +++ b/www/js/jquery-ui.min.js @@ -0,0 +1,13 @@ +/*! jQuery UI - v1.12.1 - 2016-09-14 +* http://jqueryui.com +* Includes: widget.js, position.js, data.js, disable-selection.js, effect.js, effects/effect-blind.js, effects/effect-bounce.js, effects/effect-clip.js, effects/effect-drop.js, effects/effect-explode.js, effects/effect-fade.js, effects/effect-fold.js, effects/effect-highlight.js, effects/effect-puff.js, effects/effect-pulsate.js, effects/effect-scale.js, effects/effect-shake.js, effects/effect-size.js, effects/effect-slide.js, effects/effect-transfer.js, focusable.js, form-reset-mixin.js, jquery-1-7.js, keycode.js, labels.js, scroll-parent.js, tabbable.js, unique-id.js, widgets/accordion.js, widgets/autocomplete.js, widgets/button.js, widgets/checkboxradio.js, widgets/controlgroup.js, widgets/datepicker.js, widgets/dialog.js, widgets/draggable.js, widgets/droppable.js, widgets/menu.js, widgets/mouse.js, widgets/progressbar.js, widgets/resizable.js, widgets/selectable.js, widgets/selectmenu.js, widgets/slider.js, widgets/sortable.js, widgets/spinner.js, widgets/tabs.js, widgets/tooltip.js +* Copyright jQuery Foundation and other contributors; Licensed MIT */ + +(function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)})(function(t){function e(t){for(var e=t.css("visibility");"inherit"===e;)t=t.parent(),e=t.css("visibility");return"hidden"!==e}function i(t){for(var e,i;t.length&&t[0]!==document;){if(e=t.css("position"),("absolute"===e||"relative"===e||"fixed"===e)&&(i=parseInt(t.css("zIndex"),10),!isNaN(i)&&0!==i))return i;t=t.parent()}return 0}function s(){this._curInst=null,this._keyEvent=!1,this._disabledInputs=[],this._datepickerShowing=!1,this._inDialog=!1,this._mainDivId="ui-datepicker-div",this._inlineClass="ui-datepicker-inline",this._appendClass="ui-datepicker-append",this._triggerClass="ui-datepicker-trigger",this._dialogClass="ui-datepicker-dialog",this._disableClass="ui-datepicker-disabled",this._unselectableClass="ui-datepicker-unselectable",this._currentClass="ui-datepicker-current-day",this._dayOverClass="ui-datepicker-days-cell-over",this.regional=[],this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""},this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:!1,hideIfNoPrevNext:!1,navigationAsDateFormat:!1,gotoCurrent:!1,changeMonth:!1,changeYear:!1,yearRange:"c-10:c+10",showOtherMonths:!1,selectOtherMonths:!1,showWeek:!1,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:!0,showButtonPanel:!1,autoSize:!1,disabled:!1},t.extend(this._defaults,this.regional[""]),this.regional.en=t.extend(!0,{},this.regional[""]),this.regional["en-US"]=t.extend(!0,{},this.regional.en),this.dpDiv=n(t("
"))}function n(e){var i="button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";return e.on("mouseout",i,function(){t(this).removeClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&t(this).removeClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&t(this).removeClass("ui-datepicker-next-hover")}).on("mouseover",i,o)}function o(){t.datepicker._isDisabledDatepicker(m.inline?m.dpDiv.parent()[0]:m.input[0])||(t(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover"),t(this).addClass("ui-state-hover"),-1!==this.className.indexOf("ui-datepicker-prev")&&t(this).addClass("ui-datepicker-prev-hover"),-1!==this.className.indexOf("ui-datepicker-next")&&t(this).addClass("ui-datepicker-next-hover"))}function a(e,i){t.extend(e,i);for(var s in i)null==i[s]&&(e[s]=i[s]);return e}function r(t){return function(){var e=this.element.val();t.apply(this,arguments),this._refresh(),e!==this.element.val()&&this._trigger("change")}}t.ui=t.ui||{},t.ui.version="1.12.1";var h=0,l=Array.prototype.slice;t.cleanData=function(e){return function(i){var s,n,o;for(o=0;null!=(n=i[o]);o++)try{s=t._data(n,"events"),s&&s.remove&&t(n).triggerHandler("remove")}catch(a){}e(i)}}(t.cleanData),t.widget=function(e,i,s){var n,o,a,r={},h=e.split(".")[0];e=e.split(".")[1];var l=h+"-"+e;return s||(s=i,i=t.Widget),t.isArray(s)&&(s=t.extend.apply(null,[{}].concat(s))),t.expr[":"][l.toLowerCase()]=function(e){return!!t.data(e,l)},t[h]=t[h]||{},n=t[h][e],o=t[h][e]=function(t,e){return this._createWidget?(arguments.length&&this._createWidget(t,e),void 0):new o(t,e)},t.extend(o,n,{version:s.version,_proto:t.extend({},s),_childConstructors:[]}),a=new i,a.options=t.widget.extend({},a.options),t.each(s,function(e,s){return t.isFunction(s)?(r[e]=function(){function t(){return i.prototype[e].apply(this,arguments)}function n(t){return i.prototype[e].apply(this,t)}return function(){var e,i=this._super,o=this._superApply;return this._super=t,this._superApply=n,e=s.apply(this,arguments),this._super=i,this._superApply=o,e}}(),void 0):(r[e]=s,void 0)}),o.prototype=t.widget.extend(a,{widgetEventPrefix:n?a.widgetEventPrefix||e:e},r,{constructor:o,namespace:h,widgetName:e,widgetFullName:l}),n?(t.each(n._childConstructors,function(e,i){var s=i.prototype;t.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete n._childConstructors):i._childConstructors.push(o),t.widget.bridge(e,o),o},t.widget.extend=function(e){for(var i,s,n=l.call(arguments,1),o=0,a=n.length;a>o;o++)for(i in n[o])s=n[o][i],n[o].hasOwnProperty(i)&&void 0!==s&&(e[i]=t.isPlainObject(s)?t.isPlainObject(e[i])?t.widget.extend({},e[i],s):t.widget.extend({},s):s);return e},t.widget.bridge=function(e,i){var s=i.prototype.widgetFullName||e;t.fn[e]=function(n){var o="string"==typeof n,a=l.call(arguments,1),r=this;return o?this.length||"instance"!==n?this.each(function(){var i,o=t.data(this,s);return"instance"===n?(r=o,!1):o?t.isFunction(o[n])&&"_"!==n.charAt(0)?(i=o[n].apply(o,a),i!==o&&void 0!==i?(r=i&&i.jquery?r.pushStack(i.get()):i,!1):void 0):t.error("no such method '"+n+"' for "+e+" widget instance"):t.error("cannot call methods on "+e+" prior to initialization; "+"attempted to call method '"+n+"'")}):r=void 0:(a.length&&(n=t.widget.extend.apply(null,[n].concat(a))),this.each(function(){var e=t.data(this,s);e?(e.option(n||{}),e._init&&e._init()):t.data(this,s,new i(n,this))})),r}},t.Widget=function(){},t.Widget._childConstructors=[],t.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
",options:{classes:{},disabled:!1,create:null},_createWidget:function(e,i){i=t(i||this.defaultElement||this)[0],this.element=t(i),this.uuid=h++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=t(),this.hoverable=t(),this.focusable=t(),this.classesElementLookup={},i!==this&&(t.data(i,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===i&&this.destroy()}}),this.document=t(i.style?i.ownerDocument:i.document||i),this.window=t(this.document[0].defaultView||this.document[0].parentWindow)),this.options=t.widget.extend({},this.options,this._getCreateOptions(),e),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:t.noop,_create:t.noop,_init:t.noop,destroy:function(){var e=this;this._destroy(),t.each(this.classesElementLookup,function(t,i){e._removeClass(i,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:t.noop,widget:function(){return this.element},option:function(e,i){var s,n,o,a=e;if(0===arguments.length)return t.widget.extend({},this.options);if("string"==typeof e)if(a={},s=e.split("."),e=s.shift(),s.length){for(n=a[e]=t.widget.extend({},this.options[e]),o=0;s.length-1>o;o++)n[s[o]]=n[s[o]]||{},n=n[s[o]];if(e=s.pop(),1===arguments.length)return void 0===n[e]?null:n[e];n[e]=i}else{if(1===arguments.length)return void 0===this.options[e]?null:this.options[e];a[e]=i}return this._setOptions(a),this},_setOptions:function(t){var e;for(e in t)this._setOption(e,t[e]);return this},_setOption:function(t,e){return"classes"===t&&this._setOptionClasses(e),this.options[t]=e,"disabled"===t&&this._setOptionDisabled(e),this},_setOptionClasses:function(e){var i,s,n;for(i in e)n=this.classesElementLookup[i],e[i]!==this.options.classes[i]&&n&&n.length&&(s=t(n.get()),this._removeClass(n,i),s.addClass(this._classes({element:s,keys:i,classes:e,add:!0})))},_setOptionDisabled:function(t){this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,!!t),t&&(this._removeClass(this.hoverable,null,"ui-state-hover"),this._removeClass(this.focusable,null,"ui-state-focus"))},enable:function(){return this._setOptions({disabled:!1})},disable:function(){return this._setOptions({disabled:!0})},_classes:function(e){function i(i,o){var a,r;for(r=0;i.length>r;r++)a=n.classesElementLookup[i[r]]||t(),a=e.add?t(t.unique(a.get().concat(e.element.get()))):t(a.not(e.element).get()),n.classesElementLookup[i[r]]=a,s.push(i[r]),o&&e.classes[i[r]]&&s.push(e.classes[i[r]])}var s=[],n=this;return e=t.extend({element:this.element,classes:this.options.classes||{}},e),this._on(e.element,{remove:"_untrackClassesElement"}),e.keys&&i(e.keys.match(/\S+/g)||[],!0),e.extra&&i(e.extra.match(/\S+/g)||[]),s.join(" ")},_untrackClassesElement:function(e){var i=this;t.each(i.classesElementLookup,function(s,n){-1!==t.inArray(e.target,n)&&(i.classesElementLookup[s]=t(n.not(e.target).get()))})},_removeClass:function(t,e,i){return this._toggleClass(t,e,i,!1)},_addClass:function(t,e,i){return this._toggleClass(t,e,i,!0)},_toggleClass:function(t,e,i,s){s="boolean"==typeof s?s:i;var n="string"==typeof t||null===t,o={extra:n?e:i,keys:n?t:e,element:n?this.element:t,add:s};return o.element.toggleClass(this._classes(o),s),this},_on:function(e,i,s){var n,o=this;"boolean"!=typeof e&&(s=i,i=e,e=!1),s?(i=n=t(i),this.bindings=this.bindings.add(i)):(s=i,i=this.element,n=this.widget()),t.each(s,function(s,a){function r(){return e||o.options.disabled!==!0&&!t(this).hasClass("ui-state-disabled")?("string"==typeof a?o[a]:a).apply(o,arguments):void 0}"string"!=typeof a&&(r.guid=a.guid=a.guid||r.guid||t.guid++);var h=s.match(/^([\w:-]*)\s*(.*)$/),l=h[1]+o.eventNamespace,c=h[2];c?n.on(l,c,r):i.on(l,r)})},_off:function(e,i){i=(i||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.off(i).off(i),this.bindings=t(this.bindings.not(e).get()),this.focusable=t(this.focusable.not(e).get()),this.hoverable=t(this.hoverable.not(e).get())},_delay:function(t,e){function i(){return("string"==typeof t?s[t]:t).apply(s,arguments)}var s=this;return setTimeout(i,e||0)},_hoverable:function(e){this.hoverable=this.hoverable.add(e),this._on(e,{mouseenter:function(e){this._addClass(t(e.currentTarget),null,"ui-state-hover")},mouseleave:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-hover")}})},_focusable:function(e){this.focusable=this.focusable.add(e),this._on(e,{focusin:function(e){this._addClass(t(e.currentTarget),null,"ui-state-focus")},focusout:function(e){this._removeClass(t(e.currentTarget),null,"ui-state-focus")}})},_trigger:function(e,i,s){var n,o,a=this.options[e];if(s=s||{},i=t.Event(i),i.type=(e===this.widgetEventPrefix?e:this.widgetEventPrefix+e).toLowerCase(),i.target=this.element[0],o=i.originalEvent)for(n in o)n in i||(i[n]=o[n]);return this.element.trigger(i,s),!(t.isFunction(a)&&a.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},t.each({show:"fadeIn",hide:"fadeOut"},function(e,i){t.Widget.prototype["_"+e]=function(s,n,o){"string"==typeof n&&(n={effect:n});var a,r=n?n===!0||"number"==typeof n?i:n.effect||i:e;n=n||{},"number"==typeof n&&(n={duration:n}),a=!t.isEmptyObject(n),n.complete=o,n.delay&&s.delay(n.delay),a&&t.effects&&t.effects.effect[r]?s[e](n):r!==e&&s[r]?s[r](n.duration,n.easing,o):s.queue(function(i){t(this)[e](),o&&o.call(s[0]),i()})}}),t.widget,function(){function e(t,e,i){return[parseFloat(t[0])*(u.test(t[0])?e/100:1),parseFloat(t[1])*(u.test(t[1])?i/100:1)]}function i(e,i){return parseInt(t.css(e,i),10)||0}function s(e){var i=e[0];return 9===i.nodeType?{width:e.width(),height:e.height(),offset:{top:0,left:0}}:t.isWindow(i)?{width:e.width(),height:e.height(),offset:{top:e.scrollTop(),left:e.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:e.outerWidth(),height:e.outerHeight(),offset:e.offset()}}var n,o=Math.max,a=Math.abs,r=/left|center|right/,h=/top|center|bottom/,l=/[\+\-]\d+(\.[\d]+)?%?/,c=/^\w+/,u=/%$/,d=t.fn.position;t.position={scrollbarWidth:function(){if(void 0!==n)return n;var e,i,s=t("
"),o=s.children()[0];return t("body").append(s),e=o.offsetWidth,s.css("overflow","scroll"),i=o.offsetWidth,e===i&&(i=s[0].clientWidth),s.remove(),n=e-i},getScrollInfo:function(e){var i=e.isWindow||e.isDocument?"":e.element.css("overflow-x"),s=e.isWindow||e.isDocument?"":e.element.css("overflow-y"),n="scroll"===i||"auto"===i&&e.widthi?"left":e>0?"right":"center",vertical:0>r?"top":s>0?"bottom":"middle"};l>p&&p>a(e+i)&&(u.horizontal="center"),c>f&&f>a(s+r)&&(u.vertical="middle"),u.important=o(a(e),a(i))>o(a(s),a(r))?"horizontal":"vertical",n.using.call(this,t,u)}),h.offset(t.extend(D,{using:r}))})},t.ui.position={fit:{left:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollLeft:s.offset.left,a=s.width,r=t.left-e.collisionPosition.marginLeft,h=n-r,l=r+e.collisionWidth-a-n;e.collisionWidth>a?h>0&&0>=l?(i=t.left+h+e.collisionWidth-a-n,t.left+=h-i):t.left=l>0&&0>=h?n:h>l?n+a-e.collisionWidth:n:h>0?t.left+=h:l>0?t.left-=l:t.left=o(t.left-r,t.left)},top:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollTop:s.offset.top,a=e.within.height,r=t.top-e.collisionPosition.marginTop,h=n-r,l=r+e.collisionHeight-a-n;e.collisionHeight>a?h>0&&0>=l?(i=t.top+h+e.collisionHeight-a-n,t.top+=h-i):t.top=l>0&&0>=h?n:h>l?n+a-e.collisionHeight:n:h>0?t.top+=h:l>0?t.top-=l:t.top=o(t.top-r,t.top)}},flip:{left:function(t,e){var i,s,n=e.within,o=n.offset.left+n.scrollLeft,r=n.width,h=n.isWindow?n.scrollLeft:n.offset.left,l=t.left-e.collisionPosition.marginLeft,c=l-h,u=l+e.collisionWidth-r-h,d="left"===e.my[0]?-e.elemWidth:"right"===e.my[0]?e.elemWidth:0,p="left"===e.at[0]?e.targetWidth:"right"===e.at[0]?-e.targetWidth:0,f=-2*e.offset[0];0>c?(i=t.left+d+p+f+e.collisionWidth-r-o,(0>i||a(c)>i)&&(t.left+=d+p+f)):u>0&&(s=t.left-e.collisionPosition.marginLeft+d+p+f-h,(s>0||u>a(s))&&(t.left+=d+p+f))},top:function(t,e){var i,s,n=e.within,o=n.offset.top+n.scrollTop,r=n.height,h=n.isWindow?n.scrollTop:n.offset.top,l=t.top-e.collisionPosition.marginTop,c=l-h,u=l+e.collisionHeight-r-h,d="top"===e.my[1],p=d?-e.elemHeight:"bottom"===e.my[1]?e.elemHeight:0,f="top"===e.at[1]?e.targetHeight:"bottom"===e.at[1]?-e.targetHeight:0,g=-2*e.offset[1];0>c?(s=t.top+p+f+g+e.collisionHeight-r-o,(0>s||a(c)>s)&&(t.top+=p+f+g)):u>0&&(i=t.top-e.collisionPosition.marginTop+p+f+g-h,(i>0||u>a(i))&&(t.top+=p+f+g))}},flipfit:{left:function(){t.ui.position.flip.left.apply(this,arguments),t.ui.position.fit.left.apply(this,arguments)},top:function(){t.ui.position.flip.top.apply(this,arguments),t.ui.position.fit.top.apply(this,arguments)}}}}(),t.ui.position,t.extend(t.expr[":"],{data:t.expr.createPseudo?t.expr.createPseudo(function(e){return function(i){return!!t.data(i,e)}}):function(e,i,s){return!!t.data(e,s[3])}}),t.fn.extend({disableSelection:function(){var t="onselectstart"in document.createElement("div")?"selectstart":"mousedown";return function(){return this.on(t+".ui-disableSelection",function(t){t.preventDefault()})}}(),enableSelection:function(){return this.off(".ui-disableSelection")}});var c="ui-effects-",u="ui-effects-style",d="ui-effects-animated",p=t;t.effects={effect:{}},function(t,e){function i(t,e,i){var s=u[e.type]||{};return null==t?i||!e.def?null:e.def:(t=s.floor?~~t:parseFloat(t),isNaN(t)?e.def:s.mod?(t+s.mod)%s.mod:0>t?0:t>s.max?s.max:t)}function s(i){var s=l(),n=s._rgba=[];return i=i.toLowerCase(),f(h,function(t,o){var a,r=o.re.exec(i),h=r&&o.parse(r),l=o.space||"rgba";return h?(a=s[l](h),s[c[l].cache]=a[c[l].cache],n=s._rgba=a._rgba,!1):e}),n.length?("0,0,0,0"===n.join()&&t.extend(n,o.transparent),s):o[i]}function n(t,e,i){return i=(i+1)%1,1>6*i?t+6*(e-t)*i:1>2*i?e:2>3*i?t+6*(e-t)*(2/3-i):t}var o,a="backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",r=/^([\-+])=\s*(\d+\.?\d*)/,h=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[t[1],t[2],t[3],t[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[2.55*t[1],2.55*t[2],2.55*t[3],t[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,parse:function(t){return[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])/,parse:function(t){return[parseInt(t[1]+t[1],16),parseInt(t[2]+t[2],16),parseInt(t[3]+t[3],16)]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(t){return[t[1],t[2]/100,t[3]/100,t[4]]}}],l=t.Color=function(e,i,s,n){return new t.Color.fn.parse(e,i,s,n)},c={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},u={"byte":{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},d=l.support={},p=t("

")[0],f=t.each;p.style.cssText="background-color:rgba(1,1,1,.5)",d.rgba=p.style.backgroundColor.indexOf("rgba")>-1,f(c,function(t,e){e.cache="_"+t,e.props.alpha={idx:3,type:"percent",def:1}}),l.fn=t.extend(l.prototype,{parse:function(n,a,r,h){if(n===e)return this._rgba=[null,null,null,null],this;(n.jquery||n.nodeType)&&(n=t(n).css(a),a=e);var u=this,d=t.type(n),p=this._rgba=[];return a!==e&&(n=[n,a,r,h],d="array"),"string"===d?this.parse(s(n)||o._default):"array"===d?(f(c.rgba.props,function(t,e){p[e.idx]=i(n[e.idx],e)}),this):"object"===d?(n instanceof l?f(c,function(t,e){n[e.cache]&&(u[e.cache]=n[e.cache].slice())}):f(c,function(e,s){var o=s.cache;f(s.props,function(t,e){if(!u[o]&&s.to){if("alpha"===t||null==n[t])return;u[o]=s.to(u._rgba)}u[o][e.idx]=i(n[t],e,!0)}),u[o]&&0>t.inArray(null,u[o].slice(0,3))&&(u[o][3]=1,s.from&&(u._rgba=s.from(u[o])))}),this):e},is:function(t){var i=l(t),s=!0,n=this;return f(c,function(t,o){var a,r=i[o.cache];return r&&(a=n[o.cache]||o.to&&o.to(n._rgba)||[],f(o.props,function(t,i){return null!=r[i.idx]?s=r[i.idx]===a[i.idx]:e})),s}),s},_space:function(){var t=[],e=this;return f(c,function(i,s){e[s.cache]&&t.push(i)}),t.pop()},transition:function(t,e){var s=l(t),n=s._space(),o=c[n],a=0===this.alpha()?l("transparent"):this,r=a[o.cache]||o.to(a._rgba),h=r.slice();return s=s[o.cache],f(o.props,function(t,n){var o=n.idx,a=r[o],l=s[o],c=u[n.type]||{};null!==l&&(null===a?h[o]=l:(c.mod&&(l-a>c.mod/2?a+=c.mod:a-l>c.mod/2&&(a-=c.mod)),h[o]=i((l-a)*e+a,n)))}),this[n](h)},blend:function(e){if(1===this._rgba[3])return this;var i=this._rgba.slice(),s=i.pop(),n=l(e)._rgba;return l(t.map(i,function(t,e){return(1-s)*n[e]+s*t}))},toRgbaString:function(){var e="rgba(",i=t.map(this._rgba,function(t,e){return null==t?e>2?1:0:t});return 1===i[3]&&(i.pop(),e="rgb("),e+i.join()+")"},toHslaString:function(){var e="hsla(",i=t.map(this.hsla(),function(t,e){return null==t&&(t=e>2?1:0),e&&3>e&&(t=Math.round(100*t)+"%"),t});return 1===i[3]&&(i.pop(),e="hsl("),e+i.join()+")"},toHexString:function(e){var i=this._rgba.slice(),s=i.pop();return e&&i.push(~~(255*s)),"#"+t.map(i,function(t){return t=(t||0).toString(16),1===t.length?"0"+t:t}).join("")},toString:function(){return 0===this._rgba[3]?"transparent":this.toRgbaString()}}),l.fn.parse.prototype=l.fn,c.hsla.to=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e,i,s=t[0]/255,n=t[1]/255,o=t[2]/255,a=t[3],r=Math.max(s,n,o),h=Math.min(s,n,o),l=r-h,c=r+h,u=.5*c;return e=h===r?0:s===r?60*(n-o)/l+360:n===r?60*(o-s)/l+120:60*(s-n)/l+240,i=0===l?0:.5>=u?l/c:l/(2-c),[Math.round(e)%360,i,u,null==a?1:a]},c.hsla.from=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e=t[0]/360,i=t[1],s=t[2],o=t[3],a=.5>=s?s*(1+i):s+i-s*i,r=2*s-a;return[Math.round(255*n(r,a,e+1/3)),Math.round(255*n(r,a,e)),Math.round(255*n(r,a,e-1/3)),o]},f(c,function(s,n){var o=n.props,a=n.cache,h=n.to,c=n.from;l.fn[s]=function(s){if(h&&!this[a]&&(this[a]=h(this._rgba)),s===e)return this[a].slice();var n,r=t.type(s),u="array"===r||"object"===r?s:arguments,d=this[a].slice();return f(o,function(t,e){var s=u["object"===r?t:e.idx];null==s&&(s=d[e.idx]),d[e.idx]=i(s,e)}),c?(n=l(c(d)),n[a]=d,n):l(d)},f(o,function(e,i){l.fn[e]||(l.fn[e]=function(n){var o,a=t.type(n),h="alpha"===e?this._hsla?"hsla":"rgba":s,l=this[h](),c=l[i.idx];return"undefined"===a?c:("function"===a&&(n=n.call(this,c),a=t.type(n)),null==n&&i.empty?this:("string"===a&&(o=r.exec(n),o&&(n=c+parseFloat(o[2])*("+"===o[1]?1:-1))),l[i.idx]=n,this[h](l)))})})}),l.hook=function(e){var i=e.split(" ");f(i,function(e,i){t.cssHooks[i]={set:function(e,n){var o,a,r="";if("transparent"!==n&&("string"!==t.type(n)||(o=s(n)))){if(n=l(o||n),!d.rgba&&1!==n._rgba[3]){for(a="backgroundColor"===i?e.parentNode:e;(""===r||"transparent"===r)&&a&&a.style;)try{r=t.css(a,"backgroundColor"),a=a.parentNode}catch(h){}n=n.blend(r&&"transparent"!==r?r:"_default")}n=n.toRgbaString()}try{e.style[i]=n}catch(h){}}},t.fx.step[i]=function(e){e.colorInit||(e.start=l(e.elem,i),e.end=l(e.end),e.colorInit=!0),t.cssHooks[i].set(e.elem,e.start.transition(e.end,e.pos))}})},l.hook(a),t.cssHooks.borderColor={expand:function(t){var e={};return f(["Top","Right","Bottom","Left"],function(i,s){e["border"+s+"Color"]=t}),e}},o=t.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(p),function(){function e(e){var i,s,n=e.ownerDocument.defaultView?e.ownerDocument.defaultView.getComputedStyle(e,null):e.currentStyle,o={};if(n&&n.length&&n[0]&&n[n[0]])for(s=n.length;s--;)i=n[s],"string"==typeof n[i]&&(o[t.camelCase(i)]=n[i]);else for(i in n)"string"==typeof n[i]&&(o[i]=n[i]);return o}function i(e,i){var s,o,a={};for(s in i)o=i[s],e[s]!==o&&(n[s]||(t.fx.step[s]||!isNaN(parseFloat(o)))&&(a[s]=o));return a}var s=["add","remove","toggle"],n={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};t.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(e,i){t.fx.step[i]=function(t){("none"!==t.end&&!t.setAttr||1===t.pos&&!t.setAttr)&&(p.style(t.elem,i,t.end),t.setAttr=!0)}}),t.fn.addBack||(t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.effects.animateClass=function(n,o,a,r){var h=t.speed(o,a,r);return this.queue(function(){var o,a=t(this),r=a.attr("class")||"",l=h.children?a.find("*").addBack():a;l=l.map(function(){var i=t(this);return{el:i,start:e(this)}}),o=function(){t.each(s,function(t,e){n[e]&&a[e+"Class"](n[e])})},o(),l=l.map(function(){return this.end=e(this.el[0]),this.diff=i(this.start,this.end),this}),a.attr("class",r),l=l.map(function(){var e=this,i=t.Deferred(),s=t.extend({},h,{queue:!1,complete:function(){i.resolve(e)}});return this.el.animate(this.diff,s),i.promise()}),t.when.apply(t,l.get()).done(function(){o(),t.each(arguments,function(){var e=this.el;t.each(this.diff,function(t){e.css(t,"")})}),h.complete.call(a[0])})})},t.fn.extend({addClass:function(e){return function(i,s,n,o){return s?t.effects.animateClass.call(this,{add:i},s,n,o):e.apply(this,arguments)}}(t.fn.addClass),removeClass:function(e){return function(i,s,n,o){return arguments.length>1?t.effects.animateClass.call(this,{remove:i},s,n,o):e.apply(this,arguments)}}(t.fn.removeClass),toggleClass:function(e){return function(i,s,n,o,a){return"boolean"==typeof s||void 0===s?n?t.effects.animateClass.call(this,s?{add:i}:{remove:i},n,o,a):e.apply(this,arguments):t.effects.animateClass.call(this,{toggle:i},s,n,o)}}(t.fn.toggleClass),switchClass:function(e,i,s,n,o){return t.effects.animateClass.call(this,{add:i,remove:e},s,n,o)}})}(),function(){function e(e,i,s,n){return t.isPlainObject(e)&&(i=e,e=e.effect),e={effect:e},null==i&&(i={}),t.isFunction(i)&&(n=i,s=null,i={}),("number"==typeof i||t.fx.speeds[i])&&(n=s,s=i,i={}),t.isFunction(s)&&(n=s,s=null),i&&t.extend(e,i),s=s||i.duration,e.duration=t.fx.off?0:"number"==typeof s?s:s in t.fx.speeds?t.fx.speeds[s]:t.fx.speeds._default,e.complete=n||i.complete,e}function i(e){return!e||"number"==typeof e||t.fx.speeds[e]?!0:"string"!=typeof e||t.effects.effect[e]?t.isFunction(e)?!0:"object"!=typeof e||e.effect?!1:!0:!0}function s(t,e){var i=e.outerWidth(),s=e.outerHeight(),n=/^rect\((-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto)\)$/,o=n.exec(t)||["",0,i,s,0];return{top:parseFloat(o[1])||0,right:"auto"===o[2]?i:parseFloat(o[2]),bottom:"auto"===o[3]?s:parseFloat(o[3]),left:parseFloat(o[4])||0}}t.expr&&t.expr.filters&&t.expr.filters.animated&&(t.expr.filters.animated=function(e){return function(i){return!!t(i).data(d)||e(i)}}(t.expr.filters.animated)),t.uiBackCompat!==!1&&t.extend(t.effects,{save:function(t,e){for(var i=0,s=e.length;s>i;i++)null!==e[i]&&t.data(c+e[i],t[0].style[e[i]])},restore:function(t,e){for(var i,s=0,n=e.length;n>s;s++)null!==e[s]&&(i=t.data(c+e[s]),t.css(e[s],i))},setMode:function(t,e){return"toggle"===e&&(e=t.is(":hidden")?"show":"hide"),e},createWrapper:function(e){if(e.parent().is(".ui-effects-wrapper"))return e.parent();var i={width:e.outerWidth(!0),height:e.outerHeight(!0),"float":e.css("float")},s=t("

").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),n={width:e.width(),height:e.height()},o=document.activeElement;try{o.id}catch(a){o=document.body}return e.wrap(s),(e[0]===o||t.contains(e[0],o))&&t(o).trigger("focus"),s=e.parent(),"static"===e.css("position")?(s.css({position:"relative"}),e.css({position:"relative"})):(t.extend(i,{position:e.css("position"),zIndex:e.css("z-index")}),t.each(["top","left","bottom","right"],function(t,s){i[s]=e.css(s),isNaN(parseInt(i[s],10))&&(i[s]="auto")}),e.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),e.css(n),s.css(i).show()},removeWrapper:function(e){var i=document.activeElement;return e.parent().is(".ui-effects-wrapper")&&(e.parent().replaceWith(e),(e[0]===i||t.contains(e[0],i))&&t(i).trigger("focus")),e}}),t.extend(t.effects,{version:"1.12.1",define:function(e,i,s){return s||(s=i,i="effect"),t.effects.effect[e]=s,t.effects.effect[e].mode=i,s},scaledDimensions:function(t,e,i){if(0===e)return{height:0,width:0,outerHeight:0,outerWidth:0};var s="horizontal"!==i?(e||100)/100:1,n="vertical"!==i?(e||100)/100:1;return{height:t.height()*n,width:t.width()*s,outerHeight:t.outerHeight()*n,outerWidth:t.outerWidth()*s}},clipToBox:function(t){return{width:t.clip.right-t.clip.left,height:t.clip.bottom-t.clip.top,left:t.clip.left,top:t.clip.top}},unshift:function(t,e,i){var s=t.queue();e>1&&s.splice.apply(s,[1,0].concat(s.splice(e,i))),t.dequeue()},saveStyle:function(t){t.data(u,t[0].style.cssText)},restoreStyle:function(t){t[0].style.cssText=t.data(u)||"",t.removeData(u)},mode:function(t,e){var i=t.is(":hidden");return"toggle"===e&&(e=i?"show":"hide"),(i?"hide"===e:"show"===e)&&(e="none"),e},getBaseline:function(t,e){var i,s;switch(t[0]){case"top":i=0;break;case"middle":i=.5;break;case"bottom":i=1;break;default:i=t[0]/e.height}switch(t[1]){case"left":s=0;break;case"center":s=.5;break;case"right":s=1;break;default:s=t[1]/e.width}return{x:s,y:i}},createPlaceholder:function(e){var i,s=e.css("position"),n=e.position();return e.css({marginTop:e.css("marginTop"),marginBottom:e.css("marginBottom"),marginLeft:e.css("marginLeft"),marginRight:e.css("marginRight")}).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()),/^(static|relative)/.test(s)&&(s="absolute",i=t("<"+e[0].nodeName+">").insertAfter(e).css({display:/^(inline|ruby)/.test(e.css("display"))?"inline-block":"block",visibility:"hidden",marginTop:e.css("marginTop"),marginBottom:e.css("marginBottom"),marginLeft:e.css("marginLeft"),marginRight:e.css("marginRight"),"float":e.css("float")}).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()).addClass("ui-effects-placeholder"),e.data(c+"placeholder",i)),e.css({position:s,left:n.left,top:n.top}),i},removePlaceholder:function(t){var e=c+"placeholder",i=t.data(e);i&&(i.remove(),t.removeData(e))},cleanUp:function(e){t.effects.restoreStyle(e),t.effects.removePlaceholder(e)},setTransition:function(e,i,s,n){return n=n||{},t.each(i,function(t,i){var o=e.cssUnit(i);o[0]>0&&(n[i]=o[0]*s+o[1])}),n}}),t.fn.extend({effect:function(){function i(e){function i(){r.removeData(d),t.effects.cleanUp(r),"hide"===s.mode&&r.hide(),a()}function a(){t.isFunction(h)&&h.call(r[0]),t.isFunction(e)&&e()}var r=t(this);s.mode=c.shift(),t.uiBackCompat===!1||o?"none"===s.mode?(r[l](),a()):n.call(r[0],s,i):(r.is(":hidden")?"hide"===l:"show"===l)?(r[l](),a()):n.call(r[0],s,a)}var s=e.apply(this,arguments),n=t.effects.effect[s.effect],o=n.mode,a=s.queue,r=a||"fx",h=s.complete,l=s.mode,c=[],u=function(e){var i=t(this),s=t.effects.mode(i,l)||o;i.data(d,!0),c.push(s),o&&("show"===s||s===o&&"hide"===s)&&i.show(),o&&"none"===s||t.effects.saveStyle(i),t.isFunction(e)&&e()};return t.fx.off||!n?l?this[l](s.duration,h):this.each(function(){h&&h.call(this)}):a===!1?this.each(u).each(i):this.queue(r,u).queue(r,i)},show:function(t){return function(s){if(i(s))return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="show",this.effect.call(this,n) +}}(t.fn.show),hide:function(t){return function(s){if(i(s))return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="hide",this.effect.call(this,n)}}(t.fn.hide),toggle:function(t){return function(s){if(i(s)||"boolean"==typeof s)return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="toggle",this.effect.call(this,n)}}(t.fn.toggle),cssUnit:function(e){var i=this.css(e),s=[];return t.each(["em","px","%","pt"],function(t,e){i.indexOf(e)>0&&(s=[parseFloat(i),e])}),s},cssClip:function(t){return t?this.css("clip","rect("+t.top+"px "+t.right+"px "+t.bottom+"px "+t.left+"px)"):s(this.css("clip"),this)},transfer:function(e,i){var s=t(this),n=t(e.to),o="fixed"===n.css("position"),a=t("body"),r=o?a.scrollTop():0,h=o?a.scrollLeft():0,l=n.offset(),c={top:l.top-r,left:l.left-h,height:n.innerHeight(),width:n.innerWidth()},u=s.offset(),d=t("
").appendTo("body").addClass(e.className).css({top:u.top-r,left:u.left-h,height:s.innerHeight(),width:s.innerWidth(),position:o?"fixed":"absolute"}).animate(c,e.duration,e.easing,function(){d.remove(),t.isFunction(i)&&i()})}}),t.fx.step.clip=function(e){e.clipInit||(e.start=t(e.elem).cssClip(),"string"==typeof e.end&&(e.end=s(e.end,e.elem)),e.clipInit=!0),t(e.elem).cssClip({top:e.pos*(e.end.top-e.start.top)+e.start.top,right:e.pos*(e.end.right-e.start.right)+e.start.right,bottom:e.pos*(e.end.bottom-e.start.bottom)+e.start.bottom,left:e.pos*(e.end.left-e.start.left)+e.start.left})}}(),function(){var e={};t.each(["Quad","Cubic","Quart","Quint","Expo"],function(t,i){e[i]=function(e){return Math.pow(e,t+2)}}),t.extend(e,{Sine:function(t){return 1-Math.cos(t*Math.PI/2)},Circ:function(t){return 1-Math.sqrt(1-t*t)},Elastic:function(t){return 0===t||1===t?t:-Math.pow(2,8*(t-1))*Math.sin((80*(t-1)-7.5)*Math.PI/15)},Back:function(t){return t*t*(3*t-2)},Bounce:function(t){for(var e,i=4;((e=Math.pow(2,--i))-1)/11>t;);return 1/Math.pow(4,3-i)-7.5625*Math.pow((3*e-2)/22-t,2)}}),t.each(e,function(e,i){t.easing["easeIn"+e]=i,t.easing["easeOut"+e]=function(t){return 1-i(1-t)},t.easing["easeInOut"+e]=function(t){return.5>t?i(2*t)/2:1-i(-2*t+2)/2}})}();var f=t.effects;t.effects.define("blind","hide",function(e,i){var s={up:["bottom","top"],vertical:["bottom","top"],down:["top","bottom"],left:["right","left"],horizontal:["right","left"],right:["left","right"]},n=t(this),o=e.direction||"up",a=n.cssClip(),r={clip:t.extend({},a)},h=t.effects.createPlaceholder(n);r.clip[s[o][0]]=r.clip[s[o][1]],"show"===e.mode&&(n.cssClip(r.clip),h&&h.css(t.effects.clipToBox(r)),r.clip=a),h&&h.animate(t.effects.clipToBox(r),e.duration,e.easing),n.animate(r,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("bounce",function(e,i){var s,n,o,a=t(this),r=e.mode,h="hide"===r,l="show"===r,c=e.direction||"up",u=e.distance,d=e.times||5,p=2*d+(l||h?1:0),f=e.duration/p,g=e.easing,m="up"===c||"down"===c?"top":"left",_="up"===c||"left"===c,v=0,b=a.queue().length;for(t.effects.createPlaceholder(a),o=a.css(m),u||(u=a["top"===m?"outerHeight":"outerWidth"]()/3),l&&(n={opacity:1},n[m]=o,a.css("opacity",0).css(m,_?2*-u:2*u).animate(n,f,g)),h&&(u/=Math.pow(2,d-1)),n={},n[m]=o;d>v;v++)s={},s[m]=(_?"-=":"+=")+u,a.animate(s,f,g).animate(n,f,g),u=h?2*u:u/2;h&&(s={opacity:0},s[m]=(_?"-=":"+=")+u,a.animate(s,f,g)),a.queue(i),t.effects.unshift(a,b,p+1)}),t.effects.define("clip","hide",function(e,i){var s,n={},o=t(this),a=e.direction||"vertical",r="both"===a,h=r||"horizontal"===a,l=r||"vertical"===a;s=o.cssClip(),n.clip={top:l?(s.bottom-s.top)/2:s.top,right:h?(s.right-s.left)/2:s.right,bottom:l?(s.bottom-s.top)/2:s.bottom,left:h?(s.right-s.left)/2:s.left},t.effects.createPlaceholder(o),"show"===e.mode&&(o.cssClip(n.clip),n.clip=s),o.animate(n,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("drop","hide",function(e,i){var s,n=t(this),o=e.mode,a="show"===o,r=e.direction||"left",h="up"===r||"down"===r?"top":"left",l="up"===r||"left"===r?"-=":"+=",c="+="===l?"-=":"+=",u={opacity:0};t.effects.createPlaceholder(n),s=e.distance||n["top"===h?"outerHeight":"outerWidth"](!0)/2,u[h]=l+s,a&&(n.css(u),u[h]=c+s,u.opacity=1),n.animate(u,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("explode","hide",function(e,i){function s(){b.push(this),b.length===u*d&&n()}function n(){p.css({visibility:"visible"}),t(b).remove(),i()}var o,a,r,h,l,c,u=e.pieces?Math.round(Math.sqrt(e.pieces)):3,d=u,p=t(this),f=e.mode,g="show"===f,m=p.show().css("visibility","hidden").offset(),_=Math.ceil(p.outerWidth()/d),v=Math.ceil(p.outerHeight()/u),b=[];for(o=0;u>o;o++)for(h=m.top+o*v,c=o-(u-1)/2,a=0;d>a;a++)r=m.left+a*_,l=a-(d-1)/2,p.clone().appendTo("body").wrap("
").css({position:"absolute",visibility:"visible",left:-a*_,top:-o*v}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:_,height:v,left:r+(g?l*_:0),top:h+(g?c*v:0),opacity:g?0:1}).animate({left:r+(g?0:l*_),top:h+(g?0:c*v),opacity:g?1:0},e.duration||500,e.easing,s)}),t.effects.define("fade","toggle",function(e,i){var s="show"===e.mode;t(this).css("opacity",s?0:1).animate({opacity:s?1:0},{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("fold","hide",function(e,i){var s=t(this),n=e.mode,o="show"===n,a="hide"===n,r=e.size||15,h=/([0-9]+)%/.exec(r),l=!!e.horizFirst,c=l?["right","bottom"]:["bottom","right"],u=e.duration/2,d=t.effects.createPlaceholder(s),p=s.cssClip(),f={clip:t.extend({},p)},g={clip:t.extend({},p)},m=[p[c[0]],p[c[1]]],_=s.queue().length;h&&(r=parseInt(h[1],10)/100*m[a?0:1]),f.clip[c[0]]=r,g.clip[c[0]]=r,g.clip[c[1]]=0,o&&(s.cssClip(g.clip),d&&d.css(t.effects.clipToBox(g)),g.clip=p),s.queue(function(i){d&&d.animate(t.effects.clipToBox(f),u,e.easing).animate(t.effects.clipToBox(g),u,e.easing),i()}).animate(f,u,e.easing).animate(g,u,e.easing).queue(i),t.effects.unshift(s,_,4)}),t.effects.define("highlight","show",function(e,i){var s=t(this),n={backgroundColor:s.css("backgroundColor")};"hide"===e.mode&&(n.opacity=0),t.effects.saveStyle(s),s.css({backgroundImage:"none",backgroundColor:e.color||"#ffff99"}).animate(n,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("size",function(e,i){var s,n,o,a=t(this),r=["fontSize"],h=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],l=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],c=e.mode,u="effect"!==c,d=e.scale||"both",p=e.origin||["middle","center"],f=a.css("position"),g=a.position(),m=t.effects.scaledDimensions(a),_=e.from||m,v=e.to||t.effects.scaledDimensions(a,0);t.effects.createPlaceholder(a),"show"===c&&(o=_,_=v,v=o),n={from:{y:_.height/m.height,x:_.width/m.width},to:{y:v.height/m.height,x:v.width/m.width}},("box"===d||"both"===d)&&(n.from.y!==n.to.y&&(_=t.effects.setTransition(a,h,n.from.y,_),v=t.effects.setTransition(a,h,n.to.y,v)),n.from.x!==n.to.x&&(_=t.effects.setTransition(a,l,n.from.x,_),v=t.effects.setTransition(a,l,n.to.x,v))),("content"===d||"both"===d)&&n.from.y!==n.to.y&&(_=t.effects.setTransition(a,r,n.from.y,_),v=t.effects.setTransition(a,r,n.to.y,v)),p&&(s=t.effects.getBaseline(p,m),_.top=(m.outerHeight-_.outerHeight)*s.y+g.top,_.left=(m.outerWidth-_.outerWidth)*s.x+g.left,v.top=(m.outerHeight-v.outerHeight)*s.y+g.top,v.left=(m.outerWidth-v.outerWidth)*s.x+g.left),a.css(_),("content"===d||"both"===d)&&(h=h.concat(["marginTop","marginBottom"]).concat(r),l=l.concat(["marginLeft","marginRight"]),a.find("*[width]").each(function(){var i=t(this),s=t.effects.scaledDimensions(i),o={height:s.height*n.from.y,width:s.width*n.from.x,outerHeight:s.outerHeight*n.from.y,outerWidth:s.outerWidth*n.from.x},a={height:s.height*n.to.y,width:s.width*n.to.x,outerHeight:s.height*n.to.y,outerWidth:s.width*n.to.x};n.from.y!==n.to.y&&(o=t.effects.setTransition(i,h,n.from.y,o),a=t.effects.setTransition(i,h,n.to.y,a)),n.from.x!==n.to.x&&(o=t.effects.setTransition(i,l,n.from.x,o),a=t.effects.setTransition(i,l,n.to.x,a)),u&&t.effects.saveStyle(i),i.css(o),i.animate(a,e.duration,e.easing,function(){u&&t.effects.restoreStyle(i)})})),a.animate(v,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){var e=a.offset();0===v.opacity&&a.css("opacity",_.opacity),u||(a.css("position","static"===f?"relative":f).offset(e),t.effects.saveStyle(a)),i()}})}),t.effects.define("scale",function(e,i){var s=t(this),n=e.mode,o=parseInt(e.percent,10)||(0===parseInt(e.percent,10)?0:"effect"!==n?0:100),a=t.extend(!0,{from:t.effects.scaledDimensions(s),to:t.effects.scaledDimensions(s,o,e.direction||"both"),origin:e.origin||["middle","center"]},e);e.fade&&(a.from.opacity=1,a.to.opacity=0),t.effects.effect.size.call(this,a,i)}),t.effects.define("puff","hide",function(e,i){var s=t.extend(!0,{},e,{fade:!0,percent:parseInt(e.percent,10)||150});t.effects.effect.scale.call(this,s,i)}),t.effects.define("pulsate","show",function(e,i){var s=t(this),n=e.mode,o="show"===n,a="hide"===n,r=o||a,h=2*(e.times||5)+(r?1:0),l=e.duration/h,c=0,u=1,d=s.queue().length;for((o||!s.is(":visible"))&&(s.css("opacity",0).show(),c=1);h>u;u++)s.animate({opacity:c},l,e.easing),c=1-c;s.animate({opacity:c},l,e.easing),s.queue(i),t.effects.unshift(s,d,h+1)}),t.effects.define("shake",function(e,i){var s=1,n=t(this),o=e.direction||"left",a=e.distance||20,r=e.times||3,h=2*r+1,l=Math.round(e.duration/h),c="up"===o||"down"===o?"top":"left",u="up"===o||"left"===o,d={},p={},f={},g=n.queue().length;for(t.effects.createPlaceholder(n),d[c]=(u?"-=":"+=")+a,p[c]=(u?"+=":"-=")+2*a,f[c]=(u?"-=":"+=")+2*a,n.animate(d,l,e.easing);r>s;s++)n.animate(p,l,e.easing).animate(f,l,e.easing);n.animate(p,l,e.easing).animate(d,l/2,e.easing).queue(i),t.effects.unshift(n,g,h+1)}),t.effects.define("slide","show",function(e,i){var s,n,o=t(this),a={up:["bottom","top"],down:["top","bottom"],left:["right","left"],right:["left","right"]},r=e.mode,h=e.direction||"left",l="up"===h||"down"===h?"top":"left",c="up"===h||"left"===h,u=e.distance||o["top"===l?"outerHeight":"outerWidth"](!0),d={};t.effects.createPlaceholder(o),s=o.cssClip(),n=o.position()[l],d[l]=(c?-1:1)*u+n,d.clip=o.cssClip(),d.clip[a[h][1]]=d.clip[a[h][0]],"show"===r&&(o.cssClip(d.clip),o.css(l,d[l]),d.clip=s,d[l]=n),o.animate(d,{queue:!1,duration:e.duration,easing:e.easing,complete:i})});var f;t.uiBackCompat!==!1&&(f=t.effects.define("transfer",function(e,i){t(this).transfer(e,i)})),t.ui.focusable=function(i,s){var n,o,a,r,h,l=i.nodeName.toLowerCase();return"area"===l?(n=i.parentNode,o=n.name,i.href&&o&&"map"===n.nodeName.toLowerCase()?(a=t("img[usemap='#"+o+"']"),a.length>0&&a.is(":visible")):!1):(/^(input|select|textarea|button|object)$/.test(l)?(r=!i.disabled,r&&(h=t(i).closest("fieldset")[0],h&&(r=!h.disabled))):r="a"===l?i.href||s:s,r&&t(i).is(":visible")&&e(t(i)))},t.extend(t.expr[":"],{focusable:function(e){return t.ui.focusable(e,null!=t.attr(e,"tabindex"))}}),t.ui.focusable,t.fn.form=function(){return"string"==typeof this[0].form?this.closest("form"):t(this[0].form)},t.ui.formResetMixin={_formResetHandler:function(){var e=t(this);setTimeout(function(){var i=e.data("ui-form-reset-instances");t.each(i,function(){this.refresh()})})},_bindFormResetHandler:function(){if(this.form=this.element.form(),this.form.length){var t=this.form.data("ui-form-reset-instances")||[];t.length||this.form.on("reset.ui-form-reset",this._formResetHandler),t.push(this),this.form.data("ui-form-reset-instances",t)}},_unbindFormResetHandler:function(){if(this.form.length){var e=this.form.data("ui-form-reset-instances");e.splice(t.inArray(this,e),1),e.length?this.form.data("ui-form-reset-instances",e):this.form.removeData("ui-form-reset-instances").off("reset.ui-form-reset")}}},"1.7"===t.fn.jquery.substring(0,3)&&(t.each(["Width","Height"],function(e,i){function s(e,i,s,o){return t.each(n,function(){i-=parseFloat(t.css(e,"padding"+this))||0,s&&(i-=parseFloat(t.css(e,"border"+this+"Width"))||0),o&&(i-=parseFloat(t.css(e,"margin"+this))||0)}),i}var n="Width"===i?["Left","Right"]:["Top","Bottom"],o=i.toLowerCase(),a={innerWidth:t.fn.innerWidth,innerHeight:t.fn.innerHeight,outerWidth:t.fn.outerWidth,outerHeight:t.fn.outerHeight};t.fn["inner"+i]=function(e){return void 0===e?a["inner"+i].call(this):this.each(function(){t(this).css(o,s(this,e)+"px")})},t.fn["outer"+i]=function(e,n){return"number"!=typeof e?a["outer"+i].call(this,e):this.each(function(){t(this).css(o,s(this,e,!0,n)+"px")})}}),t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.ui.keyCode={BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38},t.ui.escapeSelector=function(){var t=/([!"#$%&'()*+,.\/:;<=>?@[\]^`{|}~])/g;return function(e){return e.replace(t,"\\$1")}}(),t.fn.labels=function(){var e,i,s,n,o;return this[0].labels&&this[0].labels.length?this.pushStack(this[0].labels):(n=this.eq(0).parents("label"),s=this.attr("id"),s&&(e=this.eq(0).parents().last(),o=e.add(e.length?e.siblings():this.siblings()),i="label[for='"+t.ui.escapeSelector(s)+"']",n=n.add(o.find(i).addBack(i))),this.pushStack(n))},t.fn.scrollParent=function(e){var i=this.css("position"),s="absolute"===i,n=e?/(auto|scroll|hidden)/:/(auto|scroll)/,o=this.parents().filter(function(){var e=t(this);return s&&"static"===e.css("position")?!1:n.test(e.css("overflow")+e.css("overflow-y")+e.css("overflow-x"))}).eq(0);return"fixed"!==i&&o.length?o:t(this[0].ownerDocument||document)},t.extend(t.expr[":"],{tabbable:function(e){var i=t.attr(e,"tabindex"),s=null!=i;return(!s||i>=0)&&t.ui.focusable(e,s)}}),t.fn.extend({uniqueId:function(){var t=0;return function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++t)})}}(),removeUniqueId:function(){return this.each(function(){/^ui-id-\d+$/.test(this.id)&&t(this).removeAttr("id")})}}),t.widget("ui.accordion",{version:"1.12.1",options:{active:0,animate:{},classes:{"ui-accordion-header":"ui-corner-top","ui-accordion-header-collapsed":"ui-corner-all","ui-accordion-content":"ui-corner-bottom"},collapsible:!1,event:"click",header:"> li > :first-child, > :not(li):even",heightStyle:"auto",icons:{activeHeader:"ui-icon-triangle-1-s",header:"ui-icon-triangle-1-e"},activate:null,beforeActivate:null},hideProps:{borderTopWidth:"hide",borderBottomWidth:"hide",paddingTop:"hide",paddingBottom:"hide",height:"hide"},showProps:{borderTopWidth:"show",borderBottomWidth:"show",paddingTop:"show",paddingBottom:"show",height:"show"},_create:function(){var e=this.options;this.prevShow=this.prevHide=t(),this._addClass("ui-accordion","ui-widget ui-helper-reset"),this.element.attr("role","tablist"),e.collapsible||e.active!==!1&&null!=e.active||(e.active=0),this._processPanels(),0>e.active&&(e.active+=this.headers.length),this._refresh()},_getCreateEventData:function(){return{header:this.active,panel:this.active.length?this.active.next():t()}},_createIcons:function(){var e,i,s=this.options.icons;s&&(e=t(""),this._addClass(e,"ui-accordion-header-icon","ui-icon "+s.header),e.prependTo(this.headers),i=this.active.children(".ui-accordion-header-icon"),this._removeClass(i,s.header)._addClass(i,null,s.activeHeader)._addClass(this.headers,"ui-accordion-icons"))},_destroyIcons:function(){this._removeClass(this.headers,"ui-accordion-icons"),this.headers.children(".ui-accordion-header-icon").remove()},_destroy:function(){var t;this.element.removeAttr("role"),this.headers.removeAttr("role aria-expanded aria-selected aria-controls tabIndex").removeUniqueId(),this._destroyIcons(),t=this.headers.next().css("display","").removeAttr("role aria-hidden aria-labelledby").removeUniqueId(),"content"!==this.options.heightStyle&&t.css("height","")},_setOption:function(t,e){return"active"===t?(this._activate(e),void 0):("event"===t&&(this.options.event&&this._off(this.headers,this.options.event),this._setupEvents(e)),this._super(t,e),"collapsible"!==t||e||this.options.active!==!1||this._activate(0),"icons"===t&&(this._destroyIcons(),e&&this._createIcons()),void 0)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t),this._toggleClass(null,"ui-state-disabled",!!t),this._toggleClass(this.headers.add(this.headers.next()),null,"ui-state-disabled",!!t)},_keydown:function(e){if(!e.altKey&&!e.ctrlKey){var i=t.ui.keyCode,s=this.headers.length,n=this.headers.index(e.target),o=!1;switch(e.keyCode){case i.RIGHT:case i.DOWN:o=this.headers[(n+1)%s];break;case i.LEFT:case i.UP:o=this.headers[(n-1+s)%s];break;case i.SPACE:case i.ENTER:this._eventHandler(e);break;case i.HOME:o=this.headers[0];break;case i.END:o=this.headers[s-1]}o&&(t(e.target).attr("tabIndex",-1),t(o).attr("tabIndex",0),t(o).trigger("focus"),e.preventDefault())}},_panelKeyDown:function(e){e.keyCode===t.ui.keyCode.UP&&e.ctrlKey&&t(e.currentTarget).prev().trigger("focus")},refresh:function(){var e=this.options;this._processPanels(),e.active===!1&&e.collapsible===!0||!this.headers.length?(e.active=!1,this.active=t()):e.active===!1?this._activate(0):this.active.length&&!t.contains(this.element[0],this.active[0])?this.headers.length===this.headers.find(".ui-state-disabled").length?(e.active=!1,this.active=t()):this._activate(Math.max(0,e.active-1)):e.active=this.headers.index(this.active),this._destroyIcons(),this._refresh()},_processPanels:function(){var t=this.headers,e=this.panels;this.headers=this.element.find(this.options.header),this._addClass(this.headers,"ui-accordion-header ui-accordion-header-collapsed","ui-state-default"),this.panels=this.headers.next().filter(":not(.ui-accordion-content-active)").hide(),this._addClass(this.panels,"ui-accordion-content","ui-helper-reset ui-widget-content"),e&&(this._off(t.not(this.headers)),this._off(e.not(this.panels)))},_refresh:function(){var e,i=this.options,s=i.heightStyle,n=this.element.parent();this.active=this._findActive(i.active),this._addClass(this.active,"ui-accordion-header-active","ui-state-active")._removeClass(this.active,"ui-accordion-header-collapsed"),this._addClass(this.active.next(),"ui-accordion-content-active"),this.active.next().show(),this.headers.attr("role","tab").each(function(){var e=t(this),i=e.uniqueId().attr("id"),s=e.next(),n=s.uniqueId().attr("id");e.attr("aria-controls",n),s.attr("aria-labelledby",i)}).next().attr("role","tabpanel"),this.headers.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}).next().attr({"aria-hidden":"true"}).hide(),this.active.length?this.active.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}).next().attr({"aria-hidden":"false"}):this.headers.eq(0).attr("tabIndex",0),this._createIcons(),this._setupEvents(i.event),"fill"===s?(e=n.height(),this.element.siblings(":visible").each(function(){var i=t(this),s=i.css("position");"absolute"!==s&&"fixed"!==s&&(e-=i.outerHeight(!0))}),this.headers.each(function(){e-=t(this).outerHeight(!0)}),this.headers.next().each(function(){t(this).height(Math.max(0,e-t(this).innerHeight()+t(this).height()))}).css("overflow","auto")):"auto"===s&&(e=0,this.headers.next().each(function(){var i=t(this).is(":visible");i||t(this).show(),e=Math.max(e,t(this).css("height","").height()),i||t(this).hide()}).height(e))},_activate:function(e){var i=this._findActive(e)[0];i!==this.active[0]&&(i=i||this.active[0],this._eventHandler({target:i,currentTarget:i,preventDefault:t.noop}))},_findActive:function(e){return"number"==typeof e?this.headers.eq(e):t()},_setupEvents:function(e){var i={keydown:"_keydown"};e&&t.each(e.split(" "),function(t,e){i[e]="_eventHandler"}),this._off(this.headers.add(this.headers.next())),this._on(this.headers,i),this._on(this.headers.next(),{keydown:"_panelKeyDown"}),this._hoverable(this.headers),this._focusable(this.headers)},_eventHandler:function(e){var i,s,n=this.options,o=this.active,a=t(e.currentTarget),r=a[0]===o[0],h=r&&n.collapsible,l=h?t():a.next(),c=o.next(),u={oldHeader:o,oldPanel:c,newHeader:h?t():a,newPanel:l};e.preventDefault(),r&&!n.collapsible||this._trigger("beforeActivate",e,u)===!1||(n.active=h?!1:this.headers.index(a),this.active=r?t():a,this._toggle(u),this._removeClass(o,"ui-accordion-header-active","ui-state-active"),n.icons&&(i=o.children(".ui-accordion-header-icon"),this._removeClass(i,null,n.icons.activeHeader)._addClass(i,null,n.icons.header)),r||(this._removeClass(a,"ui-accordion-header-collapsed")._addClass(a,"ui-accordion-header-active","ui-state-active"),n.icons&&(s=a.children(".ui-accordion-header-icon"),this._removeClass(s,null,n.icons.header)._addClass(s,null,n.icons.activeHeader)),this._addClass(a.next(),"ui-accordion-content-active")))},_toggle:function(e){var i=e.newPanel,s=this.prevShow.length?this.prevShow:e.oldPanel;this.prevShow.add(this.prevHide).stop(!0,!0),this.prevShow=i,this.prevHide=s,this.options.animate?this._animate(i,s,e):(s.hide(),i.show(),this._toggleComplete(e)),s.attr({"aria-hidden":"true"}),s.prev().attr({"aria-selected":"false","aria-expanded":"false"}),i.length&&s.length?s.prev().attr({tabIndex:-1,"aria-expanded":"false"}):i.length&&this.headers.filter(function(){return 0===parseInt(t(this).attr("tabIndex"),10)}).attr("tabIndex",-1),i.attr("aria-hidden","false").prev().attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0})},_animate:function(t,e,i){var s,n,o,a=this,r=0,h=t.css("box-sizing"),l=t.length&&(!e.length||t.index()",delay:300,options:{icons:{submenu:"ui-icon-caret-1-e"},items:"> *",menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.element.uniqueId().attr({role:this.options.role,tabIndex:0}),this._addClass("ui-menu","ui-widget ui-widget-content"),this._on({"mousedown .ui-menu-item":function(t){t.preventDefault()},"click .ui-menu-item":function(e){var i=t(e.target),s=t(t.ui.safeActiveElement(this.document[0]));!this.mouseHandled&&i.not(".ui-state-disabled").length&&(this.select(e),e.isPropagationStopped()||(this.mouseHandled=!0),i.has(".ui-menu").length?this.expand(e):!this.element.is(":focus")&&s.closest(".ui-menu").length&&(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":function(e){if(!this.previousFilter){var i=t(e.target).closest(".ui-menu-item"),s=t(e.currentTarget);i[0]===s[0]&&(this._removeClass(s.siblings().children(".ui-state-active"),null,"ui-state-active"),this.focus(e,s))}},mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(t,e){var i=this.active||this.element.find(this.options.items).eq(0);e||this.focus(t,i)},blur:function(e){this._delay(function(){var i=!t.contains(this.element[0],t.ui.safeActiveElement(this.document[0]));i&&this.collapseAll(e)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(t){this._closeOnDocumentClick(t)&&this.collapseAll(t),this.mouseHandled=!1}})},_destroy:function(){var e=this.element.find(".ui-menu-item").removeAttr("role aria-disabled"),i=e.children(".ui-menu-item-wrapper").removeUniqueId().removeAttr("tabIndex role aria-haspopup");this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeAttr("role aria-labelledby aria-expanded aria-hidden aria-disabled tabIndex").removeUniqueId().show(),i.children().each(function(){var e=t(this);e.data("ui-menu-submenu-caret")&&e.remove()})},_keydown:function(e){var i,s,n,o,a=!0;switch(e.keyCode){case t.ui.keyCode.PAGE_UP:this.previousPage(e);break;case t.ui.keyCode.PAGE_DOWN:this.nextPage(e);break;case t.ui.keyCode.HOME:this._move("first","first",e);break;case t.ui.keyCode.END:this._move("last","last",e);break;case t.ui.keyCode.UP:this.previous(e);break;case t.ui.keyCode.DOWN:this.next(e);break;case t.ui.keyCode.LEFT:this.collapse(e);break;case t.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(e);break;case t.ui.keyCode.ENTER:case t.ui.keyCode.SPACE:this._activate(e);break;case t.ui.keyCode.ESCAPE:this.collapse(e);break;default:a=!1,s=this.previousFilter||"",o=!1,n=e.keyCode>=96&&105>=e.keyCode?""+(e.keyCode-96):String.fromCharCode(e.keyCode),clearTimeout(this.filterTimer),n===s?o=!0:n=s+n,i=this._filterMenuItems(n),i=o&&-1!==i.index(this.active.next())?this.active.nextAll(".ui-menu-item"):i,i.length||(n=String.fromCharCode(e.keyCode),i=this._filterMenuItems(n)),i.length?(this.focus(e,i),this.previousFilter=n,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter}a&&e.preventDefault()},_activate:function(t){this.active&&!this.active.is(".ui-state-disabled")&&(this.active.children("[aria-haspopup='true']").length?this.expand(t):this.select(t))},refresh:function(){var e,i,s,n,o,a=this,r=this.options.icons.submenu,h=this.element.find(this.options.menus);this._toggleClass("ui-menu-icons",null,!!this.element.find(".ui-icon").length),s=h.filter(":not(.ui-menu)").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var e=t(this),i=e.prev(),s=t("").data("ui-menu-submenu-caret",!0);a._addClass(s,"ui-menu-icon","ui-icon "+r),i.attr("aria-haspopup","true").prepend(s),e.attr("aria-labelledby",i.attr("id"))}),this._addClass(s,"ui-menu","ui-widget ui-widget-content ui-front"),e=h.add(this.element),i=e.find(this.options.items),i.not(".ui-menu-item").each(function(){var e=t(this);a._isDivider(e)&&a._addClass(e,"ui-menu-divider","ui-widget-content")}),n=i.not(".ui-menu-item, .ui-menu-divider"),o=n.children().not(".ui-menu").uniqueId().attr({tabIndex:-1,role:this._itemRole()}),this._addClass(n,"ui-menu-item")._addClass(o,"ui-menu-item-wrapper"),i.filter(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!t.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(t,e){if("icons"===t){var i=this.element.find(".ui-menu-icon");this._removeClass(i,null,this.options.icons.submenu)._addClass(i,null,e.submenu)}this._super(t,e)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t+""),this._toggleClass(null,"ui-state-disabled",!!t)},focus:function(t,e){var i,s,n;this.blur(t,t&&"focus"===t.type),this._scrollIntoView(e),this.active=e.first(),s=this.active.children(".ui-menu-item-wrapper"),this._addClass(s,null,"ui-state-active"),this.options.role&&this.element.attr("aria-activedescendant",s.attr("id")),n=this.active.parent().closest(".ui-menu-item").children(".ui-menu-item-wrapper"),this._addClass(n,null,"ui-state-active"),t&&"keydown"===t.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),i=e.children(".ui-menu"),i.length&&t&&/^mouse/.test(t.type)&&this._startOpening(i),this.activeMenu=e.parent(),this._trigger("focus",t,{item:e})},_scrollIntoView:function(e){var i,s,n,o,a,r;this._hasScroll()&&(i=parseFloat(t.css(this.activeMenu[0],"borderTopWidth"))||0,s=parseFloat(t.css(this.activeMenu[0],"paddingTop"))||0,n=e.offset().top-this.activeMenu.offset().top-i-s,o=this.activeMenu.scrollTop(),a=this.activeMenu.height(),r=e.outerHeight(),0>n?this.activeMenu.scrollTop(o+n):n+r>a&&this.activeMenu.scrollTop(o+n-a+r))},blur:function(t,e){e||clearTimeout(this.timer),this.active&&(this._removeClass(this.active.children(".ui-menu-item-wrapper"),null,"ui-state-active"),this._trigger("blur",t,{item:this.active}),this.active=null)},_startOpening:function(t){clearTimeout(this.timer),"true"===t.attr("aria-hidden")&&(this.timer=this._delay(function(){this._close(),this._open(t)},this.delay))},_open:function(e){var i=t.extend({of:this.active},this.options.position);clearTimeout(this.timer),this.element.find(".ui-menu").not(e.parents(".ui-menu")).hide().attr("aria-hidden","true"),e.show().removeAttr("aria-hidden").attr("aria-expanded","true").position(i)},collapseAll:function(e,i){clearTimeout(this.timer),this.timer=this._delay(function(){var s=i?this.element:t(e&&e.target).closest(this.element.find(".ui-menu"));s.length||(s=this.element),this._close(s),this.blur(e),this._removeClass(s.find(".ui-state-active"),null,"ui-state-active"),this.activeMenu=s},this.delay)},_close:function(t){t||(t=this.active?this.active.parent():this.element),t.find(".ui-menu").hide().attr("aria-hidden","true").attr("aria-expanded","false")},_closeOnDocumentClick:function(e){return!t(e.target).closest(".ui-menu").length},_isDivider:function(t){return!/[^\-\u2014\u2013\s]/.test(t.text())},collapse:function(t){var e=this.active&&this.active.parent().closest(".ui-menu-item",this.element);e&&e.length&&(this._close(),this.focus(t,e))},expand:function(t){var e=this.active&&this.active.children(".ui-menu ").find(this.options.items).first();e&&e.length&&(this._open(e.parent()),this._delay(function(){this.focus(t,e)}))},next:function(t){this._move("next","first",t)},previous:function(t){this._move("prev","last",t)},isFirstItem:function(){return this.active&&!this.active.prevAll(".ui-menu-item").length},isLastItem:function(){return this.active&&!this.active.nextAll(".ui-menu-item").length},_move:function(t,e,i){var s;this.active&&(s="first"===t||"last"===t?this.active["first"===t?"prevAll":"nextAll"](".ui-menu-item").eq(-1):this.active[t+"All"](".ui-menu-item").eq(0)),s&&s.length&&this.active||(s=this.activeMenu.find(this.options.items)[e]()),this.focus(i,s)},nextPage:function(e){var i,s,n;return this.active?(this.isLastItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.nextAll(".ui-menu-item").each(function(){return i=t(this),0>i.offset().top-s-n}),this.focus(e,i)):this.focus(e,this.activeMenu.find(this.options.items)[this.active?"last":"first"]())),void 0):(this.next(e),void 0)},previousPage:function(e){var i,s,n;return this.active?(this.isFirstItem()||(this._hasScroll()?(s=this.active.offset().top,n=this.element.height(),this.active.prevAll(".ui-menu-item").each(function(){return i=t(this),i.offset().top-s+n>0}),this.focus(e,i)):this.focus(e,this.activeMenu.find(this.options.items).first())),void 0):(this.next(e),void 0)},_hasScroll:function(){return this.element.outerHeight()",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,_create:function(){var e,i,s,n=this.element[0].nodeName.toLowerCase(),o="textarea"===n,a="input"===n; +this.isMultiLine=o||!a&&this._isContentEditable(this.element),this.valueMethod=this.element[o||a?"val":"text"],this.isNewMenu=!0,this._addClass("ui-autocomplete-input"),this.element.attr("autocomplete","off"),this._on(this.element,{keydown:function(n){if(this.element.prop("readOnly"))return e=!0,s=!0,i=!0,void 0;e=!1,s=!1,i=!1;var o=t.ui.keyCode;switch(n.keyCode){case o.PAGE_UP:e=!0,this._move("previousPage",n);break;case o.PAGE_DOWN:e=!0,this._move("nextPage",n);break;case o.UP:e=!0,this._keyEvent("previous",n);break;case o.DOWN:e=!0,this._keyEvent("next",n);break;case o.ENTER:this.menu.active&&(e=!0,n.preventDefault(),this.menu.select(n));break;case o.TAB:this.menu.active&&this.menu.select(n);break;case o.ESCAPE:this.menu.element.is(":visible")&&(this.isMultiLine||this._value(this.term),this.close(n),n.preventDefault());break;default:i=!0,this._searchTimeout(n)}},keypress:function(s){if(e)return e=!1,(!this.isMultiLine||this.menu.element.is(":visible"))&&s.preventDefault(),void 0;if(!i){var n=t.ui.keyCode;switch(s.keyCode){case n.PAGE_UP:this._move("previousPage",s);break;case n.PAGE_DOWN:this._move("nextPage",s);break;case n.UP:this._keyEvent("previous",s);break;case n.DOWN:this._keyEvent("next",s)}}},input:function(t){return s?(s=!1,t.preventDefault(),void 0):(this._searchTimeout(t),void 0)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(t){return this.cancelBlur?(delete this.cancelBlur,void 0):(clearTimeout(this.searching),this.close(t),this._change(t),void 0)}}),this._initSource(),this.menu=t("
    ").appendTo(this._appendTo()).menu({role:null}).hide().menu("instance"),this._addClass(this.menu.element,"ui-autocomplete","ui-front"),this._on(this.menu.element,{mousedown:function(e){e.preventDefault(),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur,this.element[0]!==t.ui.safeActiveElement(this.document[0])&&this.element.trigger("focus")})},menufocus:function(e,i){var s,n;return this.isNewMenu&&(this.isNewMenu=!1,e.originalEvent&&/^mouse/.test(e.originalEvent.type))?(this.menu.blur(),this.document.one("mousemove",function(){t(e.target).trigger(e.originalEvent)}),void 0):(n=i.item.data("ui-autocomplete-item"),!1!==this._trigger("focus",e,{item:n})&&e.originalEvent&&/^key/.test(e.originalEvent.type)&&this._value(n.value),s=i.item.attr("aria-label")||n.value,s&&t.trim(s).length&&(this.liveRegion.children().hide(),t("
    ").text(s).appendTo(this.liveRegion)),void 0)},menuselect:function(e,i){var s=i.item.data("ui-autocomplete-item"),n=this.previous;this.element[0]!==t.ui.safeActiveElement(this.document[0])&&(this.element.trigger("focus"),this.previous=n,this._delay(function(){this.previous=n,this.selectedItem=s})),!1!==this._trigger("select",e,{item:s})&&this._value(s.value),this.term=this._value(),this.close(e),this.selectedItem=s}}),this.liveRegion=t("
    ",{role:"status","aria-live":"assertive","aria-relevant":"additions"}).appendTo(this.document[0].body),this._addClass(this.liveRegion,null,"ui-helper-hidden-accessible"),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_destroy:function(){clearTimeout(this.searching),this.element.removeAttr("autocomplete"),this.menu.element.remove(),this.liveRegion.remove()},_setOption:function(t,e){this._super(t,e),"source"===t&&this._initSource(),"appendTo"===t&&this.menu.element.appendTo(this._appendTo()),"disabled"===t&&e&&this.xhr&&this.xhr.abort()},_isEventTargetInWidget:function(e){var i=this.menu.element[0];return e.target===this.element[0]||e.target===i||t.contains(i,e.target)},_closeOnClickOutside:function(t){this._isEventTargetInWidget(t)||this.close()},_appendTo:function(){var e=this.options.appendTo;return e&&(e=e.jquery||e.nodeType?t(e):this.document.find(e).eq(0)),e&&e[0]||(e=this.element.closest(".ui-front, dialog")),e.length||(e=this.document[0].body),e},_initSource:function(){var e,i,s=this;t.isArray(this.options.source)?(e=this.options.source,this.source=function(i,s){s(t.ui.autocomplete.filter(e,i.term))}):"string"==typeof this.options.source?(i=this.options.source,this.source=function(e,n){s.xhr&&s.xhr.abort(),s.xhr=t.ajax({url:i,data:e,dataType:"json",success:function(t){n(t)},error:function(){n([])}})}):this.source=this.options.source},_searchTimeout:function(t){clearTimeout(this.searching),this.searching=this._delay(function(){var e=this.term===this._value(),i=this.menu.element.is(":visible"),s=t.altKey||t.ctrlKey||t.metaKey||t.shiftKey;(!e||e&&!i&&!s)&&(this.selectedItem=null,this.search(null,t))},this.options.delay)},search:function(t,e){return t=null!=t?t:this._value(),this.term=this._value(),t.length").append(t("
    ").text(i.label)).appendTo(e)},_move:function(t,e){return this.menu.element.is(":visible")?this.menu.isFirstItem()&&/^previous/.test(t)||this.menu.isLastItem()&&/^next/.test(t)?(this.isMultiLine||this._value(this.term),this.menu.blur(),void 0):(this.menu[t](e),void 0):(this.search(null,e),void 0)},widget:function(){return this.menu.element},_value:function(){return this.valueMethod.apply(this.element,arguments)},_keyEvent:function(t,e){(!this.isMultiLine||this.menu.element.is(":visible"))&&(this._move(t,e),e.preventDefault())},_isContentEditable:function(t){if(!t.length)return!1;var e=t.prop("contentEditable");return"inherit"===e?this._isContentEditable(t.parent()):"true"===e}}),t.extend(t.ui.autocomplete,{escapeRegex:function(t){return t.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")},filter:function(e,i){var s=RegExp(t.ui.autocomplete.escapeRegex(i),"i");return t.grep(e,function(t){return s.test(t.label||t.value||t)})}}),t.widget("ui.autocomplete",t.ui.autocomplete,{options:{messages:{noResults:"No search results.",results:function(t){return t+(t>1?" results are":" result is")+" available, use up and down arrow keys to navigate."}}},__response:function(e){var i;this._superApply(arguments),this.options.disabled||this.cancelSearch||(i=e&&e.length?this.options.messages.results(e.length):this.options.messages.noResults,this.liveRegion.children().hide(),t("
    ").text(i).appendTo(this.liveRegion))}}),t.ui.autocomplete;var g=/ui-corner-([a-z]){2,6}/g;t.widget("ui.controlgroup",{version:"1.12.1",defaultElement:"
    ",options:{direction:"horizontal",disabled:null,onlyVisible:!0,items:{button:"input[type=button], input[type=submit], input[type=reset], button, a",controlgroupLabel:".ui-controlgroup-label",checkboxradio:"input[type='checkbox'], input[type='radio']",selectmenu:"select",spinner:".ui-spinner-input"}},_create:function(){this._enhance()},_enhance:function(){this.element.attr("role","toolbar"),this.refresh()},_destroy:function(){this._callChildMethod("destroy"),this.childWidgets.removeData("ui-controlgroup-data"),this.element.removeAttr("role"),this.options.items.controlgroupLabel&&this.element.find(this.options.items.controlgroupLabel).find(".ui-controlgroup-label-contents").contents().unwrap()},_initWidgets:function(){var e=this,i=[];t.each(this.options.items,function(s,n){var o,a={};return n?"controlgroupLabel"===s?(o=e.element.find(n),o.each(function(){var e=t(this);e.children(".ui-controlgroup-label-contents").length||e.contents().wrapAll("")}),e._addClass(o,null,"ui-widget ui-widget-content ui-state-default"),i=i.concat(o.get()),void 0):(t.fn[s]&&(a=e["_"+s+"Options"]?e["_"+s+"Options"]("middle"):{classes:{}},e.element.find(n).each(function(){var n=t(this),o=n[s]("instance"),r=t.widget.extend({},a);if("button"!==s||!n.parent(".ui-spinner").length){o||(o=n[s]()[s]("instance")),o&&(r.classes=e._resolveClassesValues(r.classes,o)),n[s](r);var h=n[s]("widget");t.data(h[0],"ui-controlgroup-data",o?o:n[s]("instance")),i.push(h[0])}})),void 0):void 0}),this.childWidgets=t(t.unique(i)),this._addClass(this.childWidgets,"ui-controlgroup-item")},_callChildMethod:function(e){this.childWidgets.each(function(){var i=t(this),s=i.data("ui-controlgroup-data");s&&s[e]&&s[e]()})},_updateCornerClass:function(t,e){var i="ui-corner-top ui-corner-bottom ui-corner-left ui-corner-right ui-corner-all",s=this._buildSimpleOptions(e,"label").classes.label;this._removeClass(t,null,i),this._addClass(t,null,s)},_buildSimpleOptions:function(t,e){var i="vertical"===this.options.direction,s={classes:{}};return s.classes[e]={middle:"",first:"ui-corner-"+(i?"top":"left"),last:"ui-corner-"+(i?"bottom":"right"),only:"ui-corner-all"}[t],s},_spinnerOptions:function(t){var e=this._buildSimpleOptions(t,"ui-spinner");return e.classes["ui-spinner-up"]="",e.classes["ui-spinner-down"]="",e},_buttonOptions:function(t){return this._buildSimpleOptions(t,"ui-button")},_checkboxradioOptions:function(t){return this._buildSimpleOptions(t,"ui-checkboxradio-label")},_selectmenuOptions:function(t){var e="vertical"===this.options.direction;return{width:e?"auto":!1,classes:{middle:{"ui-selectmenu-button-open":"","ui-selectmenu-button-closed":""},first:{"ui-selectmenu-button-open":"ui-corner-"+(e?"top":"tl"),"ui-selectmenu-button-closed":"ui-corner-"+(e?"top":"left")},last:{"ui-selectmenu-button-open":e?"":"ui-corner-tr","ui-selectmenu-button-closed":"ui-corner-"+(e?"bottom":"right")},only:{"ui-selectmenu-button-open":"ui-corner-top","ui-selectmenu-button-closed":"ui-corner-all"}}[t]}},_resolveClassesValues:function(e,i){var s={};return t.each(e,function(n){var o=i.options.classes[n]||"";o=t.trim(o.replace(g,"")),s[n]=(o+" "+e[n]).replace(/\s+/g," ")}),s},_setOption:function(t,e){return"direction"===t&&this._removeClass("ui-controlgroup-"+this.options.direction),this._super(t,e),"disabled"===t?(this._callChildMethod(e?"disable":"enable"),void 0):(this.refresh(),void 0)},refresh:function(){var e,i=this;this._addClass("ui-controlgroup ui-controlgroup-"+this.options.direction),"horizontal"===this.options.direction&&this._addClass(null,"ui-helper-clearfix"),this._initWidgets(),e=this.childWidgets,this.options.onlyVisible&&(e=e.filter(":visible")),e.length&&(t.each(["first","last"],function(t,s){var n=e[s]().data("ui-controlgroup-data");if(n&&i["_"+n.widgetName+"Options"]){var o=i["_"+n.widgetName+"Options"](1===e.length?"only":s);o.classes=i._resolveClassesValues(o.classes,n),n.element[n.widgetName](o)}else i._updateCornerClass(e[s](),s)}),this._callChildMethod("refresh"))}}),t.widget("ui.checkboxradio",[t.ui.formResetMixin,{version:"1.12.1",options:{disabled:null,label:null,icon:!0,classes:{"ui-checkboxradio-label":"ui-corner-all","ui-checkboxradio-icon":"ui-corner-all"}},_getCreateOptions:function(){var e,i,s=this,n=this._super()||{};return this._readType(),i=this.element.labels(),this.label=t(i[i.length-1]),this.label.length||t.error("No label found for checkboxradio widget"),this.originalLabel="",this.label.contents().not(this.element[0]).each(function(){s.originalLabel+=3===this.nodeType?t(this).text():this.outerHTML}),this.originalLabel&&(n.label=this.originalLabel),e=this.element[0].disabled,null!=e&&(n.disabled=e),n},_create:function(){var t=this.element[0].checked;this._bindFormResetHandler(),null==this.options.disabled&&(this.options.disabled=this.element[0].disabled),this._setOption("disabled",this.options.disabled),this._addClass("ui-checkboxradio","ui-helper-hidden-accessible"),this._addClass(this.label,"ui-checkboxradio-label","ui-button ui-widget"),"radio"===this.type&&this._addClass(this.label,"ui-checkboxradio-radio-label"),this.options.label&&this.options.label!==this.originalLabel?this._updateLabel():this.originalLabel&&(this.options.label=this.originalLabel),this._enhance(),t&&(this._addClass(this.label,"ui-checkboxradio-checked","ui-state-active"),this.icon&&this._addClass(this.icon,null,"ui-state-hover")),this._on({change:"_toggleClasses",focus:function(){this._addClass(this.label,null,"ui-state-focus ui-visual-focus")},blur:function(){this._removeClass(this.label,null,"ui-state-focus ui-visual-focus")}})},_readType:function(){var e=this.element[0].nodeName.toLowerCase();this.type=this.element[0].type,"input"===e&&/radio|checkbox/.test(this.type)||t.error("Can't create checkboxradio on element.nodeName="+e+" and element.type="+this.type)},_enhance:function(){this._updateIcon(this.element[0].checked)},widget:function(){return this.label},_getRadioGroup:function(){var e,i=this.element[0].name,s="input[name='"+t.ui.escapeSelector(i)+"']";return i?(e=this.form.length?t(this.form[0].elements).filter(s):t(s).filter(function(){return 0===t(this).form().length}),e.not(this.element)):t([])},_toggleClasses:function(){var e=this.element[0].checked;this._toggleClass(this.label,"ui-checkboxradio-checked","ui-state-active",e),this.options.icon&&"checkbox"===this.type&&this._toggleClass(this.icon,null,"ui-icon-check ui-state-checked",e)._toggleClass(this.icon,null,"ui-icon-blank",!e),"radio"===this.type&&this._getRadioGroup().each(function(){var e=t(this).checkboxradio("instance");e&&e._removeClass(e.label,"ui-checkboxradio-checked","ui-state-active")})},_destroy:function(){this._unbindFormResetHandler(),this.icon&&(this.icon.remove(),this.iconSpace.remove())},_setOption:function(t,e){return"label"!==t||e?(this._super(t,e),"disabled"===t?(this._toggleClass(this.label,null,"ui-state-disabled",e),this.element[0].disabled=e,void 0):(this.refresh(),void 0)):void 0},_updateIcon:function(e){var i="ui-icon ui-icon-background ";this.options.icon?(this.icon||(this.icon=t(""),this.iconSpace=t(" "),this._addClass(this.iconSpace,"ui-checkboxradio-icon-space")),"checkbox"===this.type?(i+=e?"ui-icon-check ui-state-checked":"ui-icon-blank",this._removeClass(this.icon,null,e?"ui-icon-blank":"ui-icon-check")):i+="ui-icon-blank",this._addClass(this.icon,"ui-checkboxradio-icon",i),e||this._removeClass(this.icon,null,"ui-icon-check ui-state-checked"),this.icon.prependTo(this.label).after(this.iconSpace)):void 0!==this.icon&&(this.icon.remove(),this.iconSpace.remove(),delete this.icon)},_updateLabel:function(){var t=this.label.contents().not(this.element[0]);this.icon&&(t=t.not(this.icon[0])),this.iconSpace&&(t=t.not(this.iconSpace[0])),t.remove(),this.label.append(this.options.label)},refresh:function(){var t=this.element[0].checked,e=this.element[0].disabled;this._updateIcon(t),this._toggleClass(this.label,"ui-checkboxradio-checked","ui-state-active",t),null!==this.options.label&&this._updateLabel(),e!==this.options.disabled&&this._setOptions({disabled:e})}}]),t.ui.checkboxradio,t.widget("ui.button",{version:"1.12.1",defaultElement:"").addClass(this._triggerClass).html(o?t("").attr({src:o,alt:n,title:n}):n)),e[r?"before":"after"](i.trigger),i.trigger.on("click",function(){return t.datepicker._datepickerShowing&&t.datepicker._lastInput===e[0]?t.datepicker._hideDatepicker():t.datepicker._datepickerShowing&&t.datepicker._lastInput!==e[0]?(t.datepicker._hideDatepicker(),t.datepicker._showDatepicker(e[0])):t.datepicker._showDatepicker(e[0]),!1}))},_autoSize:function(t){if(this._get(t,"autoSize")&&!t.inline){var e,i,s,n,o=new Date(2009,11,20),a=this._get(t,"dateFormat");a.match(/[DM]/)&&(e=function(t){for(i=0,s=0,n=0;t.length>n;n++)t[n].length>i&&(i=t[n].length,s=n);return s},o.setMonth(e(this._get(t,a.match(/MM/)?"monthNames":"monthNamesShort"))),o.setDate(e(this._get(t,a.match(/DD/)?"dayNames":"dayNamesShort"))+20-o.getDay())),t.input.attr("size",this._formatDate(t,o).length)}},_inlineDatepicker:function(e,i){var s=t(e);s.hasClass(this.markerClassName)||(s.addClass(this.markerClassName).append(i.dpDiv),t.data(e,"datepicker",i),this._setDate(i,this._getDefaultDate(i),!0),this._updateDatepicker(i),this._updateAlternate(i),i.settings.disabled&&this._disableDatepicker(e),i.dpDiv.css("display","block"))},_dialogDatepicker:function(e,i,s,n,o){var r,h,l,c,u,d=this._dialogInst;return d||(this.uuid+=1,r="dp"+this.uuid,this._dialogInput=t(""),this._dialogInput.on("keydown",this._doKeyDown),t("body").append(this._dialogInput),d=this._dialogInst=this._newInst(this._dialogInput,!1),d.settings={},t.data(this._dialogInput[0],"datepicker",d)),a(d.settings,n||{}),i=i&&i.constructor===Date?this._formatDate(d,i):i,this._dialogInput.val(i),this._pos=o?o.length?o:[o.pageX,o.pageY]:null,this._pos||(h=document.documentElement.clientWidth,l=document.documentElement.clientHeight,c=document.documentElement.scrollLeft||document.body.scrollLeft,u=document.documentElement.scrollTop||document.body.scrollTop,this._pos=[h/2-100+c,l/2-150+u]),this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px"),d.settings.onSelect=s,this._inDialog=!0,this.dpDiv.addClass(this._dialogClass),this._showDatepicker(this._dialogInput[0]),t.blockUI&&t.blockUI(this.dpDiv),t.data(this._dialogInput[0],"datepicker",d),this},_destroyDatepicker:function(e){var i,s=t(e),n=t.data(e,"datepicker");s.hasClass(this.markerClassName)&&(i=e.nodeName.toLowerCase(),t.removeData(e,"datepicker"),"input"===i?(n.append.remove(),n.trigger.remove(),s.removeClass(this.markerClassName).off("focus",this._showDatepicker).off("keydown",this._doKeyDown).off("keypress",this._doKeyPress).off("keyup",this._doKeyUp)):("div"===i||"span"===i)&&s.removeClass(this.markerClassName).empty(),m===n&&(m=null))},_enableDatepicker:function(e){var i,s,n=t(e),o=t.data(e,"datepicker");n.hasClass(this.markerClassName)&&(i=e.nodeName.toLowerCase(),"input"===i?(e.disabled=!1,o.trigger.filter("button").each(function(){this.disabled=!1}).end().filter("img").css({opacity:"1.0",cursor:""})):("div"===i||"span"===i)&&(s=n.children("."+this._inlineClass),s.children().removeClass("ui-state-disabled"),s.find("select.ui-datepicker-month, select.ui-datepicker-year").prop("disabled",!1)),this._disabledInputs=t.map(this._disabledInputs,function(t){return t===e?null:t}))},_disableDatepicker:function(e){var i,s,n=t(e),o=t.data(e,"datepicker");n.hasClass(this.markerClassName)&&(i=e.nodeName.toLowerCase(),"input"===i?(e.disabled=!0,o.trigger.filter("button").each(function(){this.disabled=!0}).end().filter("img").css({opacity:"0.5",cursor:"default"})):("div"===i||"span"===i)&&(s=n.children("."+this._inlineClass),s.children().addClass("ui-state-disabled"),s.find("select.ui-datepicker-month, select.ui-datepicker-year").prop("disabled",!0)),this._disabledInputs=t.map(this._disabledInputs,function(t){return t===e?null:t}),this._disabledInputs[this._disabledInputs.length]=e)},_isDisabledDatepicker:function(t){if(!t)return!1;for(var e=0;this._disabledInputs.length>e;e++)if(this._disabledInputs[e]===t)return!0;return!1},_getInst:function(e){try{return t.data(e,"datepicker")}catch(i){throw"Missing instance data for this datepicker"}},_optionDatepicker:function(e,i,s){var n,o,r,h,l=this._getInst(e);return 2===arguments.length&&"string"==typeof i?"defaults"===i?t.extend({},t.datepicker._defaults):l?"all"===i?t.extend({},l.settings):this._get(l,i):null:(n=i||{},"string"==typeof i&&(n={},n[i]=s),l&&(this._curInst===l&&this._hideDatepicker(),o=this._getDateDatepicker(e,!0),r=this._getMinMaxDate(l,"min"),h=this._getMinMaxDate(l,"max"),a(l.settings,n),null!==r&&void 0!==n.dateFormat&&void 0===n.minDate&&(l.settings.minDate=this._formatDate(l,r)),null!==h&&void 0!==n.dateFormat&&void 0===n.maxDate&&(l.settings.maxDate=this._formatDate(l,h)),"disabled"in n&&(n.disabled?this._disableDatepicker(e):this._enableDatepicker(e)),this._attachments(t(e),l),this._autoSize(l),this._setDate(l,o),this._updateAlternate(l),this._updateDatepicker(l)),void 0)},_changeDatepicker:function(t,e,i){this._optionDatepicker(t,e,i)},_refreshDatepicker:function(t){var e=this._getInst(t);e&&this._updateDatepicker(e)},_setDateDatepicker:function(t,e){var i=this._getInst(t);i&&(this._setDate(i,e),this._updateDatepicker(i),this._updateAlternate(i))},_getDateDatepicker:function(t,e){var i=this._getInst(t);return i&&!i.inline&&this._setDateFromField(i,e),i?this._getDate(i):null},_doKeyDown:function(e){var i,s,n,o=t.datepicker._getInst(e.target),a=!0,r=o.dpDiv.is(".ui-datepicker-rtl");if(o._keyEvent=!0,t.datepicker._datepickerShowing)switch(e.keyCode){case 9:t.datepicker._hideDatepicker(),a=!1;break;case 13:return n=t("td."+t.datepicker._dayOverClass+":not(."+t.datepicker._currentClass+")",o.dpDiv),n[0]&&t.datepicker._selectDay(e.target,o.selectedMonth,o.selectedYear,n[0]),i=t.datepicker._get(o,"onSelect"),i?(s=t.datepicker._formatDate(o),i.apply(o.input?o.input[0]:null,[s,o])):t.datepicker._hideDatepicker(),!1;case 27:t.datepicker._hideDatepicker();break;case 33:t.datepicker._adjustDate(e.target,e.ctrlKey?-t.datepicker._get(o,"stepBigMonths"):-t.datepicker._get(o,"stepMonths"),"M");break;case 34:t.datepicker._adjustDate(e.target,e.ctrlKey?+t.datepicker._get(o,"stepBigMonths"):+t.datepicker._get(o,"stepMonths"),"M");break;case 35:(e.ctrlKey||e.metaKey)&&t.datepicker._clearDate(e.target),a=e.ctrlKey||e.metaKey;break;case 36:(e.ctrlKey||e.metaKey)&&t.datepicker._gotoToday(e.target),a=e.ctrlKey||e.metaKey;break;case 37:(e.ctrlKey||e.metaKey)&&t.datepicker._adjustDate(e.target,r?1:-1,"D"),a=e.ctrlKey||e.metaKey,e.originalEvent.altKey&&t.datepicker._adjustDate(e.target,e.ctrlKey?-t.datepicker._get(o,"stepBigMonths"):-t.datepicker._get(o,"stepMonths"),"M");break;case 38:(e.ctrlKey||e.metaKey)&&t.datepicker._adjustDate(e.target,-7,"D"),a=e.ctrlKey||e.metaKey;break;case 39:(e.ctrlKey||e.metaKey)&&t.datepicker._adjustDate(e.target,r?-1:1,"D"),a=e.ctrlKey||e.metaKey,e.originalEvent.altKey&&t.datepicker._adjustDate(e.target,e.ctrlKey?+t.datepicker._get(o,"stepBigMonths"):+t.datepicker._get(o,"stepMonths"),"M");break;case 40:(e.ctrlKey||e.metaKey)&&t.datepicker._adjustDate(e.target,7,"D"),a=e.ctrlKey||e.metaKey;break;default:a=!1}else 36===e.keyCode&&e.ctrlKey?t.datepicker._showDatepicker(this):a=!1;a&&(e.preventDefault(),e.stopPropagation())},_doKeyPress:function(e){var i,s,n=t.datepicker._getInst(e.target);return t.datepicker._get(n,"constrainInput")?(i=t.datepicker._possibleChars(t.datepicker._get(n,"dateFormat")),s=String.fromCharCode(null==e.charCode?e.keyCode:e.charCode),e.ctrlKey||e.metaKey||" ">s||!i||i.indexOf(s)>-1):void 0},_doKeyUp:function(e){var i,s=t.datepicker._getInst(e.target);if(s.input.val()!==s.lastVal)try{i=t.datepicker.parseDate(t.datepicker._get(s,"dateFormat"),s.input?s.input.val():null,t.datepicker._getFormatConfig(s)),i&&(t.datepicker._setDateFromField(s),t.datepicker._updateAlternate(s),t.datepicker._updateDatepicker(s))}catch(n){}return!0},_showDatepicker:function(e){if(e=e.target||e,"input"!==e.nodeName.toLowerCase()&&(e=t("input",e.parentNode)[0]),!t.datepicker._isDisabledDatepicker(e)&&t.datepicker._lastInput!==e){var s,n,o,r,h,l,c;s=t.datepicker._getInst(e),t.datepicker._curInst&&t.datepicker._curInst!==s&&(t.datepicker._curInst.dpDiv.stop(!0,!0),s&&t.datepicker._datepickerShowing&&t.datepicker._hideDatepicker(t.datepicker._curInst.input[0])),n=t.datepicker._get(s,"beforeShow"),o=n?n.apply(e,[e,s]):{},o!==!1&&(a(s.settings,o),s.lastVal=null,t.datepicker._lastInput=e,t.datepicker._setDateFromField(s),t.datepicker._inDialog&&(e.value=""),t.datepicker._pos||(t.datepicker._pos=t.datepicker._findPos(e),t.datepicker._pos[1]+=e.offsetHeight),r=!1,t(e).parents().each(function(){return r|="fixed"===t(this).css("position"),!r}),h={left:t.datepicker._pos[0],top:t.datepicker._pos[1]},t.datepicker._pos=null,s.dpDiv.empty(),s.dpDiv.css({position:"absolute",display:"block",top:"-1000px"}),t.datepicker._updateDatepicker(s),h=t.datepicker._checkOffset(s,h,r),s.dpDiv.css({position:t.datepicker._inDialog&&t.blockUI?"static":r?"fixed":"absolute",display:"none",left:h.left+"px",top:h.top+"px"}),s.inline||(l=t.datepicker._get(s,"showAnim"),c=t.datepicker._get(s,"duration"),s.dpDiv.css("z-index",i(t(e))+1),t.datepicker._datepickerShowing=!0,t.effects&&t.effects.effect[l]?s.dpDiv.show(l,t.datepicker._get(s,"showOptions"),c):s.dpDiv[l||"show"](l?c:null),t.datepicker._shouldFocusInput(s)&&s.input.trigger("focus"),t.datepicker._curInst=s)) +}},_updateDatepicker:function(e){this.maxRows=4,m=e,e.dpDiv.empty().append(this._generateHTML(e)),this._attachHandlers(e);var i,s=this._getNumberOfMonths(e),n=s[1],a=17,r=e.dpDiv.find("."+this._dayOverClass+" a");r.length>0&&o.apply(r.get(0)),e.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""),n>1&&e.dpDiv.addClass("ui-datepicker-multi-"+n).css("width",a*n+"em"),e.dpDiv[(1!==s[0]||1!==s[1]?"add":"remove")+"Class"]("ui-datepicker-multi"),e.dpDiv[(this._get(e,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl"),e===t.datepicker._curInst&&t.datepicker._datepickerShowing&&t.datepicker._shouldFocusInput(e)&&e.input.trigger("focus"),e.yearshtml&&(i=e.yearshtml,setTimeout(function(){i===e.yearshtml&&e.yearshtml&&e.dpDiv.find("select.ui-datepicker-year:first").replaceWith(e.yearshtml),i=e.yearshtml=null},0))},_shouldFocusInput:function(t){return t.input&&t.input.is(":visible")&&!t.input.is(":disabled")&&!t.input.is(":focus")},_checkOffset:function(e,i,s){var n=e.dpDiv.outerWidth(),o=e.dpDiv.outerHeight(),a=e.input?e.input.outerWidth():0,r=e.input?e.input.outerHeight():0,h=document.documentElement.clientWidth+(s?0:t(document).scrollLeft()),l=document.documentElement.clientHeight+(s?0:t(document).scrollTop());return i.left-=this._get(e,"isRTL")?n-a:0,i.left-=s&&i.left===e.input.offset().left?t(document).scrollLeft():0,i.top-=s&&i.top===e.input.offset().top+r?t(document).scrollTop():0,i.left-=Math.min(i.left,i.left+n>h&&h>n?Math.abs(i.left+n-h):0),i.top-=Math.min(i.top,i.top+o>l&&l>o?Math.abs(o+r):0),i},_findPos:function(e){for(var i,s=this._getInst(e),n=this._get(s,"isRTL");e&&("hidden"===e.type||1!==e.nodeType||t.expr.filters.hidden(e));)e=e[n?"previousSibling":"nextSibling"];return i=t(e).offset(),[i.left,i.top]},_hideDatepicker:function(e){var i,s,n,o,a=this._curInst;!a||e&&a!==t.data(e,"datepicker")||this._datepickerShowing&&(i=this._get(a,"showAnim"),s=this._get(a,"duration"),n=function(){t.datepicker._tidyDialog(a)},t.effects&&(t.effects.effect[i]||t.effects[i])?a.dpDiv.hide(i,t.datepicker._get(a,"showOptions"),s,n):a.dpDiv["slideDown"===i?"slideUp":"fadeIn"===i?"fadeOut":"hide"](i?s:null,n),i||n(),this._datepickerShowing=!1,o=this._get(a,"onClose"),o&&o.apply(a.input?a.input[0]:null,[a.input?a.input.val():"",a]),this._lastInput=null,this._inDialog&&(this._dialogInput.css({position:"absolute",left:"0",top:"-100px"}),t.blockUI&&(t.unblockUI(),t("body").append(this.dpDiv))),this._inDialog=!1)},_tidyDialog:function(t){t.dpDiv.removeClass(this._dialogClass).off(".ui-datepicker-calendar")},_checkExternalClick:function(e){if(t.datepicker._curInst){var i=t(e.target),s=t.datepicker._getInst(i[0]);(i[0].id!==t.datepicker._mainDivId&&0===i.parents("#"+t.datepicker._mainDivId).length&&!i.hasClass(t.datepicker.markerClassName)&&!i.closest("."+t.datepicker._triggerClass).length&&t.datepicker._datepickerShowing&&(!t.datepicker._inDialog||!t.blockUI)||i.hasClass(t.datepicker.markerClassName)&&t.datepicker._curInst!==s)&&t.datepicker._hideDatepicker()}},_adjustDate:function(e,i,s){var n=t(e),o=this._getInst(n[0]);this._isDisabledDatepicker(n[0])||(this._adjustInstDate(o,i+("M"===s?this._get(o,"showCurrentAtPos"):0),s),this._updateDatepicker(o))},_gotoToday:function(e){var i,s=t(e),n=this._getInst(s[0]);this._get(n,"gotoCurrent")&&n.currentDay?(n.selectedDay=n.currentDay,n.drawMonth=n.selectedMonth=n.currentMonth,n.drawYear=n.selectedYear=n.currentYear):(i=new Date,n.selectedDay=i.getDate(),n.drawMonth=n.selectedMonth=i.getMonth(),n.drawYear=n.selectedYear=i.getFullYear()),this._notifyChange(n),this._adjustDate(s)},_selectMonthYear:function(e,i,s){var n=t(e),o=this._getInst(n[0]);o["selected"+("M"===s?"Month":"Year")]=o["draw"+("M"===s?"Month":"Year")]=parseInt(i.options[i.selectedIndex].value,10),this._notifyChange(o),this._adjustDate(n)},_selectDay:function(e,i,s,n){var o,a=t(e);t(n).hasClass(this._unselectableClass)||this._isDisabledDatepicker(a[0])||(o=this._getInst(a[0]),o.selectedDay=o.currentDay=t("a",n).html(),o.selectedMonth=o.currentMonth=i,o.selectedYear=o.currentYear=s,this._selectDate(e,this._formatDate(o,o.currentDay,o.currentMonth,o.currentYear)))},_clearDate:function(e){var i=t(e);this._selectDate(i,"")},_selectDate:function(e,i){var s,n=t(e),o=this._getInst(n[0]);i=null!=i?i:this._formatDate(o),o.input&&o.input.val(i),this._updateAlternate(o),s=this._get(o,"onSelect"),s?s.apply(o.input?o.input[0]:null,[i,o]):o.input&&o.input.trigger("change"),o.inline?this._updateDatepicker(o):(this._hideDatepicker(),this._lastInput=o.input[0],"object"!=typeof o.input[0]&&o.input.trigger("focus"),this._lastInput=null)},_updateAlternate:function(e){var i,s,n,o=this._get(e,"altField");o&&(i=this._get(e,"altFormat")||this._get(e,"dateFormat"),s=this._getDate(e),n=this.formatDate(i,s,this._getFormatConfig(e)),t(o).val(n))},noWeekends:function(t){var e=t.getDay();return[e>0&&6>e,""]},iso8601Week:function(t){var e,i=new Date(t.getTime());return i.setDate(i.getDate()+4-(i.getDay()||7)),e=i.getTime(),i.setMonth(0),i.setDate(1),Math.floor(Math.round((e-i)/864e5)/7)+1},parseDate:function(e,i,s){if(null==e||null==i)throw"Invalid arguments";if(i="object"==typeof i?""+i:i+"",""===i)return null;var n,o,a,r,h=0,l=(s?s.shortYearCutoff:null)||this._defaults.shortYearCutoff,c="string"!=typeof l?l:(new Date).getFullYear()%100+parseInt(l,10),u=(s?s.dayNamesShort:null)||this._defaults.dayNamesShort,d=(s?s.dayNames:null)||this._defaults.dayNames,p=(s?s.monthNamesShort:null)||this._defaults.monthNamesShort,f=(s?s.monthNames:null)||this._defaults.monthNames,g=-1,m=-1,_=-1,v=-1,b=!1,y=function(t){var i=e.length>n+1&&e.charAt(n+1)===t;return i&&n++,i},w=function(t){var e=y(t),s="@"===t?14:"!"===t?20:"y"===t&&e?4:"o"===t?3:2,n="y"===t?s:1,o=RegExp("^\\d{"+n+","+s+"}"),a=i.substring(h).match(o);if(!a)throw"Missing number at position "+h;return h+=a[0].length,parseInt(a[0],10)},k=function(e,s,n){var o=-1,a=t.map(y(e)?n:s,function(t,e){return[[e,t]]}).sort(function(t,e){return-(t[1].length-e[1].length)});if(t.each(a,function(t,e){var s=e[1];return i.substr(h,s.length).toLowerCase()===s.toLowerCase()?(o=e[0],h+=s.length,!1):void 0}),-1!==o)return o+1;throw"Unknown name at position "+h},x=function(){if(i.charAt(h)!==e.charAt(n))throw"Unexpected literal at position "+h;h++};for(n=0;e.length>n;n++)if(b)"'"!==e.charAt(n)||y("'")?x():b=!1;else switch(e.charAt(n)){case"d":_=w("d");break;case"D":k("D",u,d);break;case"o":v=w("o");break;case"m":m=w("m");break;case"M":m=k("M",p,f);break;case"y":g=w("y");break;case"@":r=new Date(w("@")),g=r.getFullYear(),m=r.getMonth()+1,_=r.getDate();break;case"!":r=new Date((w("!")-this._ticksTo1970)/1e4),g=r.getFullYear(),m=r.getMonth()+1,_=r.getDate();break;case"'":y("'")?x():b=!0;break;default:x()}if(i.length>h&&(a=i.substr(h),!/^\s+/.test(a)))throw"Extra/unparsed characters found in date: "+a;if(-1===g?g=(new Date).getFullYear():100>g&&(g+=(new Date).getFullYear()-(new Date).getFullYear()%100+(c>=g?0:-100)),v>-1)for(m=1,_=v;;){if(o=this._getDaysInMonth(g,m-1),o>=_)break;m++,_-=o}if(r=this._daylightSavingAdjust(new Date(g,m-1,_)),r.getFullYear()!==g||r.getMonth()+1!==m||r.getDate()!==_)throw"Invalid date";return r},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:1e7*60*60*24*(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925)),formatDate:function(t,e,i){if(!e)return"";var s,n=(i?i.dayNamesShort:null)||this._defaults.dayNamesShort,o=(i?i.dayNames:null)||this._defaults.dayNames,a=(i?i.monthNamesShort:null)||this._defaults.monthNamesShort,r=(i?i.monthNames:null)||this._defaults.monthNames,h=function(e){var i=t.length>s+1&&t.charAt(s+1)===e;return i&&s++,i},l=function(t,e,i){var s=""+e;if(h(t))for(;i>s.length;)s="0"+s;return s},c=function(t,e,i,s){return h(t)?s[e]:i[e]},u="",d=!1;if(e)for(s=0;t.length>s;s++)if(d)"'"!==t.charAt(s)||h("'")?u+=t.charAt(s):d=!1;else switch(t.charAt(s)){case"d":u+=l("d",e.getDate(),2);break;case"D":u+=c("D",e.getDay(),n,o);break;case"o":u+=l("o",Math.round((new Date(e.getFullYear(),e.getMonth(),e.getDate()).getTime()-new Date(e.getFullYear(),0,0).getTime())/864e5),3);break;case"m":u+=l("m",e.getMonth()+1,2);break;case"M":u+=c("M",e.getMonth(),a,r);break;case"y":u+=h("y")?e.getFullYear():(10>e.getFullYear()%100?"0":"")+e.getFullYear()%100;break;case"@":u+=e.getTime();break;case"!":u+=1e4*e.getTime()+this._ticksTo1970;break;case"'":h("'")?u+="'":d=!0;break;default:u+=t.charAt(s)}return u},_possibleChars:function(t){var e,i="",s=!1,n=function(i){var s=t.length>e+1&&t.charAt(e+1)===i;return s&&e++,s};for(e=0;t.length>e;e++)if(s)"'"!==t.charAt(e)||n("'")?i+=t.charAt(e):s=!1;else switch(t.charAt(e)){case"d":case"m":case"y":case"@":i+="0123456789";break;case"D":case"M":return null;case"'":n("'")?i+="'":s=!0;break;default:i+=t.charAt(e)}return i},_get:function(t,e){return void 0!==t.settings[e]?t.settings[e]:this._defaults[e]},_setDateFromField:function(t,e){if(t.input.val()!==t.lastVal){var i=this._get(t,"dateFormat"),s=t.lastVal=t.input?t.input.val():null,n=this._getDefaultDate(t),o=n,a=this._getFormatConfig(t);try{o=this.parseDate(i,s,a)||n}catch(r){s=e?"":s}t.selectedDay=o.getDate(),t.drawMonth=t.selectedMonth=o.getMonth(),t.drawYear=t.selectedYear=o.getFullYear(),t.currentDay=s?o.getDate():0,t.currentMonth=s?o.getMonth():0,t.currentYear=s?o.getFullYear():0,this._adjustInstDate(t)}},_getDefaultDate:function(t){return this._restrictMinMax(t,this._determineDate(t,this._get(t,"defaultDate"),new Date))},_determineDate:function(e,i,s){var n=function(t){var e=new Date;return e.setDate(e.getDate()+t),e},o=function(i){try{return t.datepicker.parseDate(t.datepicker._get(e,"dateFormat"),i,t.datepicker._getFormatConfig(e))}catch(s){}for(var n=(i.toLowerCase().match(/^c/)?t.datepicker._getDate(e):null)||new Date,o=n.getFullYear(),a=n.getMonth(),r=n.getDate(),h=/([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,l=h.exec(i);l;){switch(l[2]||"d"){case"d":case"D":r+=parseInt(l[1],10);break;case"w":case"W":r+=7*parseInt(l[1],10);break;case"m":case"M":a+=parseInt(l[1],10),r=Math.min(r,t.datepicker._getDaysInMonth(o,a));break;case"y":case"Y":o+=parseInt(l[1],10),r=Math.min(r,t.datepicker._getDaysInMonth(o,a))}l=h.exec(i)}return new Date(o,a,r)},a=null==i||""===i?s:"string"==typeof i?o(i):"number"==typeof i?isNaN(i)?s:n(i):new Date(i.getTime());return a=a&&"Invalid Date"==""+a?s:a,a&&(a.setHours(0),a.setMinutes(0),a.setSeconds(0),a.setMilliseconds(0)),this._daylightSavingAdjust(a)},_daylightSavingAdjust:function(t){return t?(t.setHours(t.getHours()>12?t.getHours()+2:0),t):null},_setDate:function(t,e,i){var s=!e,n=t.selectedMonth,o=t.selectedYear,a=this._restrictMinMax(t,this._determineDate(t,e,new Date));t.selectedDay=t.currentDay=a.getDate(),t.drawMonth=t.selectedMonth=t.currentMonth=a.getMonth(),t.drawYear=t.selectedYear=t.currentYear=a.getFullYear(),n===t.selectedMonth&&o===t.selectedYear||i||this._notifyChange(t),this._adjustInstDate(t),t.input&&t.input.val(s?"":this._formatDate(t))},_getDate:function(t){var e=!t.currentYear||t.input&&""===t.input.val()?null:this._daylightSavingAdjust(new Date(t.currentYear,t.currentMonth,t.currentDay));return e},_attachHandlers:function(e){var i=this._get(e,"stepMonths"),s="#"+e.id.replace(/\\\\/g,"\\");e.dpDiv.find("[data-handler]").map(function(){var e={prev:function(){t.datepicker._adjustDate(s,-i,"M")},next:function(){t.datepicker._adjustDate(s,+i,"M")},hide:function(){t.datepicker._hideDatepicker()},today:function(){t.datepicker._gotoToday(s)},selectDay:function(){return t.datepicker._selectDay(s,+this.getAttribute("data-month"),+this.getAttribute("data-year"),this),!1},selectMonth:function(){return t.datepicker._selectMonthYear(s,this,"M"),!1},selectYear:function(){return t.datepicker._selectMonthYear(s,this,"Y"),!1}};t(this).on(this.getAttribute("data-event"),e[this.getAttribute("data-handler")])})},_generateHTML:function(t){var e,i,s,n,o,a,r,h,l,c,u,d,p,f,g,m,_,v,b,y,w,k,x,C,D,I,T,P,M,S,H,z,O,A,N,W,E,F,L,R=new Date,B=this._daylightSavingAdjust(new Date(R.getFullYear(),R.getMonth(),R.getDate())),Y=this._get(t,"isRTL"),j=this._get(t,"showButtonPanel"),q=this._get(t,"hideIfNoPrevNext"),K=this._get(t,"navigationAsDateFormat"),U=this._getNumberOfMonths(t),V=this._get(t,"showCurrentAtPos"),$=this._get(t,"stepMonths"),X=1!==U[0]||1!==U[1],G=this._daylightSavingAdjust(t.currentDay?new Date(t.currentYear,t.currentMonth,t.currentDay):new Date(9999,9,9)),Q=this._getMinMaxDate(t,"min"),J=this._getMinMaxDate(t,"max"),Z=t.drawMonth-V,te=t.drawYear;if(0>Z&&(Z+=12,te--),J)for(e=this._daylightSavingAdjust(new Date(J.getFullYear(),J.getMonth()-U[0]*U[1]+1,J.getDate())),e=Q&&Q>e?Q:e;this._daylightSavingAdjust(new Date(te,Z,1))>e;)Z--,0>Z&&(Z=11,te--);for(t.drawMonth=Z,t.drawYear=te,i=this._get(t,"prevText"),i=K?this.formatDate(i,this._daylightSavingAdjust(new Date(te,Z-$,1)),this._getFormatConfig(t)):i,s=this._canAdjustMonth(t,-1,te,Z)?""+i+"":q?"":""+i+"",n=this._get(t,"nextText"),n=K?this.formatDate(n,this._daylightSavingAdjust(new Date(te,Z+$,1)),this._getFormatConfig(t)):n,o=this._canAdjustMonth(t,1,te,Z)?""+n+"":q?"":""+n+"",a=this._get(t,"currentText"),r=this._get(t,"gotoCurrent")&&t.currentDay?G:B,a=K?this.formatDate(a,r,this._getFormatConfig(t)):a,h=t.inline?"":"",l=j?"
    "+(Y?h:"")+(this._isInRange(t,r)?"":"")+(Y?"":h)+"
    ":"",c=parseInt(this._get(t,"firstDay"),10),c=isNaN(c)?0:c,u=this._get(t,"showWeek"),d=this._get(t,"dayNames"),p=this._get(t,"dayNamesMin"),f=this._get(t,"monthNames"),g=this._get(t,"monthNamesShort"),m=this._get(t,"beforeShowDay"),_=this._get(t,"showOtherMonths"),v=this._get(t,"selectOtherMonths"),b=this._getDefaultDate(t),y="",k=0;U[0]>k;k++){for(x="",this.maxRows=4,C=0;U[1]>C;C++){if(D=this._daylightSavingAdjust(new Date(te,Z,t.selectedDay)),I=" ui-corner-all",T="",X){if(T+="
    "}for(T+="
    "+(/all|left/.test(I)&&0===k?Y?o:s:"")+(/all|right/.test(I)&&0===k?Y?s:o:"")+this._generateMonthYearHeader(t,Z,te,Q,J,k>0||C>0,f,g)+"
    "+"",P=u?"":"",w=0;7>w;w++)M=(w+c)%7,P+="";for(T+=P+"",S=this._getDaysInMonth(te,Z),te===t.selectedYear&&Z===t.selectedMonth&&(t.selectedDay=Math.min(t.selectedDay,S)),H=(this._getFirstDayOfMonth(te,Z)-c+7)%7,z=Math.ceil((H+S)/7),O=X?this.maxRows>z?this.maxRows:z:z,this.maxRows=O,A=this._daylightSavingAdjust(new Date(te,Z,1-H)),N=0;O>N;N++){for(T+="",W=u?"":"",w=0;7>w;w++)E=m?m.apply(t.input?t.input[0]:null,[A]):[!0,""],F=A.getMonth()!==Z,L=F&&!v||!E[0]||Q&&Q>A||J&&A>J,W+="",A.setDate(A.getDate()+1),A=this._daylightSavingAdjust(A);T+=W+""}Z++,Z>11&&(Z=0,te++),T+="
    "+this._get(t,"weekHeader")+"=5?" class='ui-datepicker-week-end'":"")+">"+""+p[M]+"
    "+this._get(t,"calculateWeek")(A)+""+(F&&!_?" ":L?""+A.getDate()+"":""+A.getDate()+"")+"
    "+(X?"
    "+(U[0]>0&&C===U[1]-1?"
    ":""):""),x+=T}y+=x}return y+=l,t._keyEvent=!1,y},_generateMonthYearHeader:function(t,e,i,s,n,o,a,r){var h,l,c,u,d,p,f,g,m=this._get(t,"changeMonth"),_=this._get(t,"changeYear"),v=this._get(t,"showMonthAfterYear"),b="
    ",y="";if(o||!m)y+=""+a[e]+"";else{for(h=s&&s.getFullYear()===i,l=n&&n.getFullYear()===i,y+=""}if(v||(b+=y+(!o&&m&&_?"":" ")),!t.yearshtml)if(t.yearshtml="",o||!_)b+=""+i+"";else{for(u=this._get(t,"yearRange").split(":"),d=(new Date).getFullYear(),p=function(t){var e=t.match(/c[+\-].*/)?i+parseInt(t.substring(1),10):t.match(/[+\-].*/)?d+parseInt(t,10):parseInt(t,10);return isNaN(e)?d:e},f=p(u[0]),g=Math.max(f,p(u[1]||"")),f=s?Math.max(f,s.getFullYear()):f,g=n?Math.min(g,n.getFullYear()):g,t.yearshtml+="",b+=t.yearshtml,t.yearshtml=null}return b+=this._get(t,"yearSuffix"),v&&(b+=(!o&&m&&_?"":" ")+y),b+="
    "},_adjustInstDate:function(t,e,i){var s=t.selectedYear+("Y"===i?e:0),n=t.selectedMonth+("M"===i?e:0),o=Math.min(t.selectedDay,this._getDaysInMonth(s,n))+("D"===i?e:0),a=this._restrictMinMax(t,this._daylightSavingAdjust(new Date(s,n,o)));t.selectedDay=a.getDate(),t.drawMonth=t.selectedMonth=a.getMonth(),t.drawYear=t.selectedYear=a.getFullYear(),("M"===i||"Y"===i)&&this._notifyChange(t)},_restrictMinMax:function(t,e){var i=this._getMinMaxDate(t,"min"),s=this._getMinMaxDate(t,"max"),n=i&&i>e?i:e;return s&&n>s?s:n},_notifyChange:function(t){var e=this._get(t,"onChangeMonthYear");e&&e.apply(t.input?t.input[0]:null,[t.selectedYear,t.selectedMonth+1,t])},_getNumberOfMonths:function(t){var e=this._get(t,"numberOfMonths");return null==e?[1,1]:"number"==typeof e?[1,e]:e},_getMinMaxDate:function(t,e){return this._determineDate(t,this._get(t,e+"Date"),null)},_getDaysInMonth:function(t,e){return 32-this._daylightSavingAdjust(new Date(t,e,32)).getDate()},_getFirstDayOfMonth:function(t,e){return new Date(t,e,1).getDay()},_canAdjustMonth:function(t,e,i,s){var n=this._getNumberOfMonths(t),o=this._daylightSavingAdjust(new Date(i,s+(0>e?e:n[0]*n[1]),1));return 0>e&&o.setDate(this._getDaysInMonth(o.getFullYear(),o.getMonth())),this._isInRange(t,o)},_isInRange:function(t,e){var i,s,n=this._getMinMaxDate(t,"min"),o=this._getMinMaxDate(t,"max"),a=null,r=null,h=this._get(t,"yearRange");return h&&(i=h.split(":"),s=(new Date).getFullYear(),a=parseInt(i[0],10),r=parseInt(i[1],10),i[0].match(/[+\-].*/)&&(a+=s),i[1].match(/[+\-].*/)&&(r+=s)),(!n||e.getTime()>=n.getTime())&&(!o||e.getTime()<=o.getTime())&&(!a||e.getFullYear()>=a)&&(!r||r>=e.getFullYear())},_getFormatConfig:function(t){var e=this._get(t,"shortYearCutoff");return e="string"!=typeof e?e:(new Date).getFullYear()%100+parseInt(e,10),{shortYearCutoff:e,dayNamesShort:this._get(t,"dayNamesShort"),dayNames:this._get(t,"dayNames"),monthNamesShort:this._get(t,"monthNamesShort"),monthNames:this._get(t,"monthNames")}},_formatDate:function(t,e,i,s){e||(t.currentDay=t.selectedDay,t.currentMonth=t.selectedMonth,t.currentYear=t.selectedYear);var n=e?"object"==typeof e?e:this._daylightSavingAdjust(new Date(s,i,e)):this._daylightSavingAdjust(new Date(t.currentYear,t.currentMonth,t.currentDay));return this.formatDate(this._get(t,"dateFormat"),n,this._getFormatConfig(t))}}),t.fn.datepicker=function(e){if(!this.length)return this;t.datepicker.initialized||(t(document).on("mousedown",t.datepicker._checkExternalClick),t.datepicker.initialized=!0),0===t("#"+t.datepicker._mainDivId).length&&t("body").append(t.datepicker.dpDiv);var i=Array.prototype.slice.call(arguments,1);return"string"!=typeof e||"isDisabled"!==e&&"getDate"!==e&&"widget"!==e?"option"===e&&2===arguments.length&&"string"==typeof arguments[1]?t.datepicker["_"+e+"Datepicker"].apply(t.datepicker,[this[0]].concat(i)):this.each(function(){"string"==typeof e?t.datepicker["_"+e+"Datepicker"].apply(t.datepicker,[this].concat(i)):t.datepicker._attachDatepicker(this,e)}):t.datepicker["_"+e+"Datepicker"].apply(t.datepicker,[this[0]].concat(i))},t.datepicker=new s,t.datepicker.initialized=!1,t.datepicker.uuid=(new Date).getTime(),t.datepicker.version="1.12.1",t.datepicker,t.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase());var _=!1;t(document).on("mouseup",function(){_=!1}),t.widget("ui.mouse",{version:"1.12.1",options:{cancel:"input, textarea, button, select, option",distance:1,delay:0},_mouseInit:function(){var e=this;this.element.on("mousedown."+this.widgetName,function(t){return e._mouseDown(t)}).on("click."+this.widgetName,function(i){return!0===t.data(i.target,e.widgetName+".preventClickEvent")?(t.removeData(i.target,e.widgetName+".preventClickEvent"),i.stopImmediatePropagation(),!1):void 0}),this.started=!1},_mouseDestroy:function(){this.element.off("."+this.widgetName),this._mouseMoveDelegate&&this.document.off("mousemove."+this.widgetName,this._mouseMoveDelegate).off("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(e){if(!_){this._mouseMoved=!1,this._mouseStarted&&this._mouseUp(e),this._mouseDownEvent=e;var i=this,s=1===e.which,n="string"==typeof this.options.cancel&&e.target.nodeName?t(e.target).closest(this.options.cancel).length:!1;return s&&!n&&this._mouseCapture(e)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){i.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(e)!==!1,!this._mouseStarted)?(e.preventDefault(),!0):(!0===t.data(e.target,this.widgetName+".preventClickEvent")&&t.removeData(e.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(t){return i._mouseMove(t)},this._mouseUpDelegate=function(t){return i._mouseUp(t)},this.document.on("mousemove."+this.widgetName,this._mouseMoveDelegate).on("mouseup."+this.widgetName,this._mouseUpDelegate),e.preventDefault(),_=!0,!0)):!0}},_mouseMove:function(e){if(this._mouseMoved){if(t.ui.ie&&(!document.documentMode||9>document.documentMode)&&!e.button)return this._mouseUp(e);if(!e.which)if(e.originalEvent.altKey||e.originalEvent.ctrlKey||e.originalEvent.metaKey||e.originalEvent.shiftKey)this.ignoreMissingWhich=!0;else if(!this.ignoreMissingWhich)return this._mouseUp(e)}return(e.which||e.button)&&(this._mouseMoved=!0),this._mouseStarted?(this._mouseDrag(e),e.preventDefault()):(this._mouseDistanceMet(e)&&this._mouseDelayMet(e)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,e)!==!1,this._mouseStarted?this._mouseDrag(e):this._mouseUp(e)),!this._mouseStarted)},_mouseUp:function(e){this.document.off("mousemove."+this.widgetName,this._mouseMoveDelegate).off("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,e.target===this._mouseDownEvent.target&&t.data(e.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(e)),this._mouseDelayTimer&&(clearTimeout(this._mouseDelayTimer),delete this._mouseDelayTimer),this.ignoreMissingWhich=!1,_=!1,e.preventDefault()},_mouseDistanceMet:function(t){return Math.max(Math.abs(this._mouseDownEvent.pageX-t.pageX),Math.abs(this._mouseDownEvent.pageY-t.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}}),t.ui.plugin={add:function(e,i,s){var n,o=t.ui[e].prototype;for(n in s)o.plugins[n]=o.plugins[n]||[],o.plugins[n].push([i,s[n]])},call:function(t,e,i,s){var n,o=t.plugins[e];if(o&&(s||t.element[0].parentNode&&11!==t.element[0].parentNode.nodeType))for(n=0;o.length>n;n++)t.options[o[n][0]]&&o[n][1].apply(t.element,i)}},t.ui.safeBlur=function(e){e&&"body"!==e.nodeName.toLowerCase()&&t(e).trigger("blur")},t.widget("ui.draggable",t.ui.mouse,{version:"1.12.1",widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1,drag:null,start:null,stop:null},_create:function(){"original"===this.options.helper&&this._setPositionRelative(),this.options.addClasses&&this._addClass("ui-draggable"),this._setHandleClassName(),this._mouseInit()},_setOption:function(t,e){this._super(t,e),"handle"===t&&(this._removeHandleClassName(),this._setHandleClassName())},_destroy:function(){return(this.helper||this.element).is(".ui-draggable-dragging")?(this.destroyOnClear=!0,void 0):(this._removeHandleClassName(),this._mouseDestroy(),void 0)},_mouseCapture:function(e){var i=this.options;return this.helper||i.disabled||t(e.target).closest(".ui-resizable-handle").length>0?!1:(this.handle=this._getHandle(e),this.handle?(this._blurActiveElement(e),this._blockFrames(i.iframeFix===!0?"iframe":i.iframeFix),!0):!1)},_blockFrames:function(e){this.iframeBlocks=this.document.find(e).map(function(){var e=t(this);return t("
    ").css("position","absolute").appendTo(e.parent()).outerWidth(e.outerWidth()).outerHeight(e.outerHeight()).offset(e.offset())[0]})},_unblockFrames:function(){this.iframeBlocks&&(this.iframeBlocks.remove(),delete this.iframeBlocks)},_blurActiveElement:function(e){var i=t.ui.safeActiveElement(this.document[0]),s=t(e.target);s.closest(i).length||t.ui.safeBlur(i)},_mouseStart:function(e){var i=this.options;return this.helper=this._createHelper(e),this._addClass(this.helper,"ui-draggable-dragging"),this._cacheHelperProportions(),t.ui.ddmanager&&(t.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(!0),this.offsetParent=this.helper.offsetParent(),this.hasFixedAncestor=this.helper.parents().filter(function(){return"fixed"===t(this).css("position")}).length>0,this.positionAbs=this.element.offset(),this._refreshOffsets(e),this.originalPosition=this.position=this._generatePosition(e,!1),this.originalPageX=e.pageX,this.originalPageY=e.pageY,i.cursorAt&&this._adjustOffsetFromHelper(i.cursorAt),this._setContainment(),this._trigger("start",e)===!1?(this._clear(),!1):(this._cacheHelperProportions(),t.ui.ddmanager&&!i.dropBehaviour&&t.ui.ddmanager.prepareOffsets(this,e),this._mouseDrag(e,!0),t.ui.ddmanager&&t.ui.ddmanager.dragStart(this,e),!0)},_refreshOffsets:function(t){this.offset={top:this.positionAbs.top-this.margins.top,left:this.positionAbs.left-this.margins.left,scroll:!1,parent:this._getParentOffset(),relative:this._getRelativeOffset()},this.offset.click={left:t.pageX-this.offset.left,top:t.pageY-this.offset.top}},_mouseDrag:function(e,i){if(this.hasFixedAncestor&&(this.offset.parent=this._getParentOffset()),this.position=this._generatePosition(e,!0),this.positionAbs=this._convertPositionTo("absolute"),!i){var s=this._uiHash();if(this._trigger("drag",e,s)===!1)return this._mouseUp(new t.Event("mouseup",e)),!1;this.position=s.position}return this.helper[0].style.left=this.position.left+"px",this.helper[0].style.top=this.position.top+"px",t.ui.ddmanager&&t.ui.ddmanager.drag(this,e),!1},_mouseStop:function(e){var i=this,s=!1;return t.ui.ddmanager&&!this.options.dropBehaviour&&(s=t.ui.ddmanager.drop(this,e)),this.dropped&&(s=this.dropped,this.dropped=!1),"invalid"===this.options.revert&&!s||"valid"===this.options.revert&&s||this.options.revert===!0||t.isFunction(this.options.revert)&&this.options.revert.call(this.element,s)?t(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){i._trigger("stop",e)!==!1&&i._clear()}):this._trigger("stop",e)!==!1&&this._clear(),!1},_mouseUp:function(e){return this._unblockFrames(),t.ui.ddmanager&&t.ui.ddmanager.dragStop(this,e),this.handleElement.is(e.target)&&this.element.trigger("focus"),t.ui.mouse.prototype._mouseUp.call(this,e)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp(new t.Event("mouseup",{target:this.element[0]})):this._clear(),this},_getHandle:function(e){return this.options.handle?!!t(e.target).closest(this.element.find(this.options.handle)).length:!0},_setHandleClassName:function(){this.handleElement=this.options.handle?this.element.find(this.options.handle):this.element,this._addClass(this.handleElement,"ui-draggable-handle")},_removeHandleClassName:function(){this._removeClass(this.handleElement,"ui-draggable-handle")},_createHelper:function(e){var i=this.options,s=t.isFunction(i.helper),n=s?t(i.helper.apply(this.element[0],[e])):"clone"===i.helper?this.element.clone().removeAttr("id"):this.element;return n.parents("body").length||n.appendTo("parent"===i.appendTo?this.element[0].parentNode:i.appendTo),s&&n[0]===this.element[0]&&this._setPositionRelative(),n[0]===this.element[0]||/(fixed|absolute)/.test(n.css("position"))||n.css("position","absolute"),n},_setPositionRelative:function(){/^(?:r|a|f)/.test(this.element.css("position"))||(this.element[0].style.position="relative")},_adjustOffsetFromHelper:function(e){"string"==typeof e&&(e=e.split(" ")),t.isArray(e)&&(e={left:+e[0],top:+e[1]||0}),"left"in e&&(this.offset.click.left=e.left+this.margins.left),"right"in e&&(this.offset.click.left=this.helperProportions.width-e.right+this.margins.left),"top"in e&&(this.offset.click.top=e.top+this.margins.top),"bottom"in e&&(this.offset.click.top=this.helperProportions.height-e.bottom+this.margins.top)},_isRootNode:function(t){return/(html|body)/i.test(t.tagName)||t===this.document[0]},_getParentOffset:function(){var e=this.offsetParent.offset(),i=this.document[0];return"absolute"===this.cssPosition&&this.scrollParent[0]!==i&&t.contains(this.scrollParent[0],this.offsetParent[0])&&(e.left+=this.scrollParent.scrollLeft(),e.top+=this.scrollParent.scrollTop()),this._isRootNode(this.offsetParent[0])&&(e={top:0,left:0}),{top:e.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:e.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"!==this.cssPosition)return{top:0,left:0};var t=this.element.position(),e=this._isRootNode(this.scrollParent[0]);return{top:t.top-(parseInt(this.helper.css("top"),10)||0)+(e?0:this.scrollParent.scrollTop()),left:t.left-(parseInt(this.helper.css("left"),10)||0)+(e?0:this.scrollParent.scrollLeft())} +},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var e,i,s,n=this.options,o=this.document[0];return this.relativeContainer=null,n.containment?"window"===n.containment?(this.containment=[t(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,t(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,t(window).scrollLeft()+t(window).width()-this.helperProportions.width-this.margins.left,t(window).scrollTop()+(t(window).height()||o.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],void 0):"document"===n.containment?(this.containment=[0,0,t(o).width()-this.helperProportions.width-this.margins.left,(t(o).height()||o.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],void 0):n.containment.constructor===Array?(this.containment=n.containment,void 0):("parent"===n.containment&&(n.containment=this.helper[0].parentNode),i=t(n.containment),s=i[0],s&&(e=/(scroll|auto)/.test(i.css("overflow")),this.containment=[(parseInt(i.css("borderLeftWidth"),10)||0)+(parseInt(i.css("paddingLeft"),10)||0),(parseInt(i.css("borderTopWidth"),10)||0)+(parseInt(i.css("paddingTop"),10)||0),(e?Math.max(s.scrollWidth,s.offsetWidth):s.offsetWidth)-(parseInt(i.css("borderRightWidth"),10)||0)-(parseInt(i.css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(e?Math.max(s.scrollHeight,s.offsetHeight):s.offsetHeight)-(parseInt(i.css("borderBottomWidth"),10)||0)-(parseInt(i.css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relativeContainer=i),void 0):(this.containment=null,void 0)},_convertPositionTo:function(t,e){e||(e=this.position);var i="absolute"===t?1:-1,s=this._isRootNode(this.scrollParent[0]);return{top:e.top+this.offset.relative.top*i+this.offset.parent.top*i-("fixed"===this.cssPosition?-this.offset.scroll.top:s?0:this.offset.scroll.top)*i,left:e.left+this.offset.relative.left*i+this.offset.parent.left*i-("fixed"===this.cssPosition?-this.offset.scroll.left:s?0:this.offset.scroll.left)*i}},_generatePosition:function(t,e){var i,s,n,o,a=this.options,r=this._isRootNode(this.scrollParent[0]),h=t.pageX,l=t.pageY;return r&&this.offset.scroll||(this.offset.scroll={top:this.scrollParent.scrollTop(),left:this.scrollParent.scrollLeft()}),e&&(this.containment&&(this.relativeContainer?(s=this.relativeContainer.offset(),i=[this.containment[0]+s.left,this.containment[1]+s.top,this.containment[2]+s.left,this.containment[3]+s.top]):i=this.containment,t.pageX-this.offset.click.lefti[2]&&(h=i[2]+this.offset.click.left),t.pageY-this.offset.click.top>i[3]&&(l=i[3]+this.offset.click.top)),a.grid&&(n=a.grid[1]?this.originalPageY+Math.round((l-this.originalPageY)/a.grid[1])*a.grid[1]:this.originalPageY,l=i?n-this.offset.click.top>=i[1]||n-this.offset.click.top>i[3]?n:n-this.offset.click.top>=i[1]?n-a.grid[1]:n+a.grid[1]:n,o=a.grid[0]?this.originalPageX+Math.round((h-this.originalPageX)/a.grid[0])*a.grid[0]:this.originalPageX,h=i?o-this.offset.click.left>=i[0]||o-this.offset.click.left>i[2]?o:o-this.offset.click.left>=i[0]?o-a.grid[0]:o+a.grid[0]:o),"y"===a.axis&&(h=this.originalPageX),"x"===a.axis&&(l=this.originalPageY)),{top:l-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.offset.scroll.top:r?0:this.offset.scroll.top),left:h-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.offset.scroll.left:r?0:this.offset.scroll.left)}},_clear:function(){this._removeClass(this.helper,"ui-draggable-dragging"),this.helper[0]===this.element[0]||this.cancelHelperRemoval||this.helper.remove(),this.helper=null,this.cancelHelperRemoval=!1,this.destroyOnClear&&this.destroy()},_trigger:function(e,i,s){return s=s||this._uiHash(),t.ui.plugin.call(this,e,[i,s,this],!0),/^(drag|start|stop)/.test(e)&&(this.positionAbs=this._convertPositionTo("absolute"),s.offset=this.positionAbs),t.Widget.prototype._trigger.call(this,e,i,s)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}}),t.ui.plugin.add("draggable","connectToSortable",{start:function(e,i,s){var n=t.extend({},i,{item:s.element});s.sortables=[],t(s.options.connectToSortable).each(function(){var i=t(this).sortable("instance");i&&!i.options.disabled&&(s.sortables.push(i),i.refreshPositions(),i._trigger("activate",e,n))})},stop:function(e,i,s){var n=t.extend({},i,{item:s.element});s.cancelHelperRemoval=!1,t.each(s.sortables,function(){var t=this;t.isOver?(t.isOver=0,s.cancelHelperRemoval=!0,t.cancelHelperRemoval=!1,t._storedCSS={position:t.placeholder.css("position"),top:t.placeholder.css("top"),left:t.placeholder.css("left")},t._mouseStop(e),t.options.helper=t.options._helper):(t.cancelHelperRemoval=!0,t._trigger("deactivate",e,n))})},drag:function(e,i,s){t.each(s.sortables,function(){var n=!1,o=this;o.positionAbs=s.positionAbs,o.helperProportions=s.helperProportions,o.offset.click=s.offset.click,o._intersectsWith(o.containerCache)&&(n=!0,t.each(s.sortables,function(){return this.positionAbs=s.positionAbs,this.helperProportions=s.helperProportions,this.offset.click=s.offset.click,this!==o&&this._intersectsWith(this.containerCache)&&t.contains(o.element[0],this.element[0])&&(n=!1),n})),n?(o.isOver||(o.isOver=1,s._parent=i.helper.parent(),o.currentItem=i.helper.appendTo(o.element).data("ui-sortable-item",!0),o.options._helper=o.options.helper,o.options.helper=function(){return i.helper[0]},e.target=o.currentItem[0],o._mouseCapture(e,!0),o._mouseStart(e,!0,!0),o.offset.click.top=s.offset.click.top,o.offset.click.left=s.offset.click.left,o.offset.parent.left-=s.offset.parent.left-o.offset.parent.left,o.offset.parent.top-=s.offset.parent.top-o.offset.parent.top,s._trigger("toSortable",e),s.dropped=o.element,t.each(s.sortables,function(){this.refreshPositions()}),s.currentItem=s.element,o.fromOutside=s),o.currentItem&&(o._mouseDrag(e),i.position=o.position)):o.isOver&&(o.isOver=0,o.cancelHelperRemoval=!0,o.options._revert=o.options.revert,o.options.revert=!1,o._trigger("out",e,o._uiHash(o)),o._mouseStop(e,!0),o.options.revert=o.options._revert,o.options.helper=o.options._helper,o.placeholder&&o.placeholder.remove(),i.helper.appendTo(s._parent),s._refreshOffsets(e),i.position=s._generatePosition(e,!0),s._trigger("fromSortable",e),s.dropped=!1,t.each(s.sortables,function(){this.refreshPositions()}))})}}),t.ui.plugin.add("draggable","cursor",{start:function(e,i,s){var n=t("body"),o=s.options;n.css("cursor")&&(o._cursor=n.css("cursor")),n.css("cursor",o.cursor)},stop:function(e,i,s){var n=s.options;n._cursor&&t("body").css("cursor",n._cursor)}}),t.ui.plugin.add("draggable","opacity",{start:function(e,i,s){var n=t(i.helper),o=s.options;n.css("opacity")&&(o._opacity=n.css("opacity")),n.css("opacity",o.opacity)},stop:function(e,i,s){var n=s.options;n._opacity&&t(i.helper).css("opacity",n._opacity)}}),t.ui.plugin.add("draggable","scroll",{start:function(t,e,i){i.scrollParentNotHidden||(i.scrollParentNotHidden=i.helper.scrollParent(!1)),i.scrollParentNotHidden[0]!==i.document[0]&&"HTML"!==i.scrollParentNotHidden[0].tagName&&(i.overflowOffset=i.scrollParentNotHidden.offset())},drag:function(e,i,s){var n=s.options,o=!1,a=s.scrollParentNotHidden[0],r=s.document[0];a!==r&&"HTML"!==a.tagName?(n.axis&&"x"===n.axis||(s.overflowOffset.top+a.offsetHeight-e.pageY=0;d--)h=s.snapElements[d].left-s.margins.left,l=h+s.snapElements[d].width,c=s.snapElements[d].top-s.margins.top,u=c+s.snapElements[d].height,h-g>_||m>l+g||c-g>b||v>u+g||!t.contains(s.snapElements[d].item.ownerDocument,s.snapElements[d].item)?(s.snapElements[d].snapping&&s.options.snap.release&&s.options.snap.release.call(s.element,e,t.extend(s._uiHash(),{snapItem:s.snapElements[d].item})),s.snapElements[d].snapping=!1):("inner"!==f.snapMode&&(n=g>=Math.abs(c-b),o=g>=Math.abs(u-v),a=g>=Math.abs(h-_),r=g>=Math.abs(l-m),n&&(i.position.top=s._convertPositionTo("relative",{top:c-s.helperProportions.height,left:0}).top),o&&(i.position.top=s._convertPositionTo("relative",{top:u,left:0}).top),a&&(i.position.left=s._convertPositionTo("relative",{top:0,left:h-s.helperProportions.width}).left),r&&(i.position.left=s._convertPositionTo("relative",{top:0,left:l}).left)),p=n||o||a||r,"outer"!==f.snapMode&&(n=g>=Math.abs(c-v),o=g>=Math.abs(u-b),a=g>=Math.abs(h-m),r=g>=Math.abs(l-_),n&&(i.position.top=s._convertPositionTo("relative",{top:c,left:0}).top),o&&(i.position.top=s._convertPositionTo("relative",{top:u-s.helperProportions.height,left:0}).top),a&&(i.position.left=s._convertPositionTo("relative",{top:0,left:h}).left),r&&(i.position.left=s._convertPositionTo("relative",{top:0,left:l-s.helperProportions.width}).left)),!s.snapElements[d].snapping&&(n||o||a||r||p)&&s.options.snap.snap&&s.options.snap.snap.call(s.element,e,t.extend(s._uiHash(),{snapItem:s.snapElements[d].item})),s.snapElements[d].snapping=n||o||a||r||p)}}),t.ui.plugin.add("draggable","stack",{start:function(e,i,s){var n,o=s.options,a=t.makeArray(t(o.stack)).sort(function(e,i){return(parseInt(t(e).css("zIndex"),10)||0)-(parseInt(t(i).css("zIndex"),10)||0)});a.length&&(n=parseInt(t(a[0]).css("zIndex"),10)||0,t(a).each(function(e){t(this).css("zIndex",n+e)}),this.css("zIndex",n+a.length))}}),t.ui.plugin.add("draggable","zIndex",{start:function(e,i,s){var n=t(i.helper),o=s.options;n.css("zIndex")&&(o._zIndex=n.css("zIndex")),n.css("zIndex",o.zIndex)},stop:function(e,i,s){var n=s.options;n._zIndex&&t(i.helper).css("zIndex",n._zIndex)}}),t.ui.draggable,t.widget("ui.resizable",t.ui.mouse,{version:"1.12.1",widgetEventPrefix:"resize",options:{alsoResize:!1,animate:!1,animateDuration:"slow",animateEasing:"swing",aspectRatio:!1,autoHide:!1,classes:{"ui-resizable-se":"ui-icon ui-icon-gripsmall-diagonal-se"},containment:!1,ghost:!1,grid:!1,handles:"e,s,se",helper:!1,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:90,resize:null,start:null,stop:null},_num:function(t){return parseFloat(t)||0},_isNumber:function(t){return!isNaN(parseFloat(t))},_hasScroll:function(e,i){if("hidden"===t(e).css("overflow"))return!1;var s=i&&"left"===i?"scrollLeft":"scrollTop",n=!1;return e[s]>0?!0:(e[s]=1,n=e[s]>0,e[s]=0,n)},_create:function(){var e,i=this.options,s=this;this._addClass("ui-resizable"),t.extend(this,{_aspectRatio:!!i.aspectRatio,aspectRatio:i.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:i.helper||i.ghost||i.animate?i.helper||"ui-resizable-helper":null}),this.element[0].nodeName.match(/^(canvas|textarea|input|select|button|img)$/i)&&(this.element.wrap(t("
    ").css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("ui-resizable",this.element.resizable("instance")),this.elementIsWrapper=!0,e={marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom"),marginLeft:this.originalElement.css("marginLeft")},this.element.css(e),this.originalElement.css("margin",0),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css(e),this._proportionallyResize()),this._setupHandles(),i.autoHide&&t(this.element).on("mouseenter",function(){i.disabled||(s._removeClass("ui-resizable-autohide"),s._handles.show())}).on("mouseleave",function(){i.disabled||s.resizing||(s._addClass("ui-resizable-autohide"),s._handles.hide())}),this._mouseInit()},_destroy:function(){this._mouseDestroy();var e,i=function(e){t(e).removeData("resizable").removeData("ui-resizable").off(".resizable").find(".ui-resizable-handle").remove()};return this.elementIsWrapper&&(i(this.element),e=this.element,this.originalElement.css({position:e.css("position"),width:e.outerWidth(),height:e.outerHeight(),top:e.css("top"),left:e.css("left")}).insertAfter(e),e.remove()),this.originalElement.css("resize",this.originalResizeStyle),i(this.originalElement),this},_setOption:function(t,e){switch(this._super(t,e),t){case"handles":this._removeHandles(),this._setupHandles();break;default:}},_setupHandles:function(){var e,i,s,n,o,a=this.options,r=this;if(this.handles=a.handles||(t(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se"),this._handles=t(),this.handles.constructor===String)for("all"===this.handles&&(this.handles="n,e,s,w,se,sw,ne,nw"),s=this.handles.split(","),this.handles={},i=0;s.length>i;i++)e=t.trim(s[i]),n="ui-resizable-"+e,o=t("
    "),this._addClass(o,"ui-resizable-handle "+n),o.css({zIndex:a.zIndex}),this.handles[e]=".ui-resizable-"+e,this.element.append(o);this._renderAxis=function(e){var i,s,n,o;e=e||this.element;for(i in this.handles)this.handles[i].constructor===String?this.handles[i]=this.element.children(this.handles[i]).first().show():(this.handles[i].jquery||this.handles[i].nodeType)&&(this.handles[i]=t(this.handles[i]),this._on(this.handles[i],{mousedown:r._mouseDown})),this.elementIsWrapper&&this.originalElement[0].nodeName.match(/^(textarea|input|select|button)$/i)&&(s=t(this.handles[i],this.element),o=/sw|ne|nw|se|n|s/.test(i)?s.outerHeight():s.outerWidth(),n=["padding",/ne|nw|n/.test(i)?"Top":/se|sw|s/.test(i)?"Bottom":/^e$/.test(i)?"Right":"Left"].join(""),e.css(n,o),this._proportionallyResize()),this._handles=this._handles.add(this.handles[i])},this._renderAxis(this.element),this._handles=this._handles.add(this.element.find(".ui-resizable-handle")),this._handles.disableSelection(),this._handles.on("mouseover",function(){r.resizing||(this.className&&(o=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)),r.axis=o&&o[1]?o[1]:"se")}),a.autoHide&&(this._handles.hide(),this._addClass("ui-resizable-autohide"))},_removeHandles:function(){this._handles.remove()},_mouseCapture:function(e){var i,s,n=!1;for(i in this.handles)s=t(this.handles[i])[0],(s===e.target||t.contains(s,e.target))&&(n=!0);return!this.options.disabled&&n},_mouseStart:function(e){var i,s,n,o=this.options,a=this.element;return this.resizing=!0,this._renderProxy(),i=this._num(this.helper.css("left")),s=this._num(this.helper.css("top")),o.containment&&(i+=t(o.containment).scrollLeft()||0,s+=t(o.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:i,top:s},this.size=this._helper?{width:this.helper.width(),height:this.helper.height()}:{width:a.width(),height:a.height()},this.originalSize=this._helper?{width:a.outerWidth(),height:a.outerHeight()}:{width:a.width(),height:a.height()},this.sizeDiff={width:a.outerWidth()-a.width(),height:a.outerHeight()-a.height()},this.originalPosition={left:i,top:s},this.originalMousePosition={left:e.pageX,top:e.pageY},this.aspectRatio="number"==typeof o.aspectRatio?o.aspectRatio:this.originalSize.width/this.originalSize.height||1,n=t(".ui-resizable-"+this.axis).css("cursor"),t("body").css("cursor","auto"===n?this.axis+"-resize":n),this._addClass("ui-resizable-resizing"),this._propagate("start",e),!0},_mouseDrag:function(e){var i,s,n=this.originalMousePosition,o=this.axis,a=e.pageX-n.left||0,r=e.pageY-n.top||0,h=this._change[o];return this._updatePrevProperties(),h?(i=h.apply(this,[e,a,r]),this._updateVirtualBoundaries(e.shiftKey),(this._aspectRatio||e.shiftKey)&&(i=this._updateRatio(i,e)),i=this._respectSize(i,e),this._updateCache(i),this._propagate("resize",e),s=this._applyChanges(),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),t.isEmptyObject(s)||(this._updatePrevProperties(),this._trigger("resize",e,this.ui()),this._applyChanges()),!1):!1},_mouseStop:function(e){this.resizing=!1;var i,s,n,o,a,r,h,l=this.options,c=this;return this._helper&&(i=this._proportionallyResizeElements,s=i.length&&/textarea/i.test(i[0].nodeName),n=s&&this._hasScroll(i[0],"left")?0:c.sizeDiff.height,o=s?0:c.sizeDiff.width,a={width:c.helper.width()-o,height:c.helper.height()-n},r=parseFloat(c.element.css("left"))+(c.position.left-c.originalPosition.left)||null,h=parseFloat(c.element.css("top"))+(c.position.top-c.originalPosition.top)||null,l.animate||this.element.css(t.extend(a,{top:h,left:r})),c.helper.height(c.size.height),c.helper.width(c.size.width),this._helper&&!l.animate&&this._proportionallyResize()),t("body").css("cursor","auto"),this._removeClass("ui-resizable-resizing"),this._propagate("stop",e),this._helper&&this.helper.remove(),!1},_updatePrevProperties:function(){this.prevPosition={top:this.position.top,left:this.position.left},this.prevSize={width:this.size.width,height:this.size.height}},_applyChanges:function(){var t={};return this.position.top!==this.prevPosition.top&&(t.top=this.position.top+"px"),this.position.left!==this.prevPosition.left&&(t.left=this.position.left+"px"),this.size.width!==this.prevSize.width&&(t.width=this.size.width+"px"),this.size.height!==this.prevSize.height&&(t.height=this.size.height+"px"),this.helper.css(t),t},_updateVirtualBoundaries:function(t){var e,i,s,n,o,a=this.options;o={minWidth:this._isNumber(a.minWidth)?a.minWidth:0,maxWidth:this._isNumber(a.maxWidth)?a.maxWidth:1/0,minHeight:this._isNumber(a.minHeight)?a.minHeight:0,maxHeight:this._isNumber(a.maxHeight)?a.maxHeight:1/0},(this._aspectRatio||t)&&(e=o.minHeight*this.aspectRatio,s=o.minWidth/this.aspectRatio,i=o.maxHeight*this.aspectRatio,n=o.maxWidth/this.aspectRatio,e>o.minWidth&&(o.minWidth=e),s>o.minHeight&&(o.minHeight=s),o.maxWidth>i&&(o.maxWidth=i),o.maxHeight>n&&(o.maxHeight=n)),this._vBoundaries=o},_updateCache:function(t){this.offset=this.helper.offset(),this._isNumber(t.left)&&(this.position.left=t.left),this._isNumber(t.top)&&(this.position.top=t.top),this._isNumber(t.height)&&(this.size.height=t.height),this._isNumber(t.width)&&(this.size.width=t.width)},_updateRatio:function(t){var e=this.position,i=this.size,s=this.axis;return this._isNumber(t.height)?t.width=t.height*this.aspectRatio:this._isNumber(t.width)&&(t.height=t.width/this.aspectRatio),"sw"===s&&(t.left=e.left+(i.width-t.width),t.top=null),"nw"===s&&(t.top=e.top+(i.height-t.height),t.left=e.left+(i.width-t.width)),t},_respectSize:function(t){var e=this._vBoundaries,i=this.axis,s=this._isNumber(t.width)&&e.maxWidth&&e.maxWidtht.width,a=this._isNumber(t.height)&&e.minHeight&&e.minHeight>t.height,r=this.originalPosition.left+this.originalSize.width,h=this.originalPosition.top+this.originalSize.height,l=/sw|nw|w/.test(i),c=/nw|ne|n/.test(i);return o&&(t.width=e.minWidth),a&&(t.height=e.minHeight),s&&(t.width=e.maxWidth),n&&(t.height=e.maxHeight),o&&l&&(t.left=r-e.minWidth),s&&l&&(t.left=r-e.maxWidth),a&&c&&(t.top=h-e.minHeight),n&&c&&(t.top=h-e.maxHeight),t.width||t.height||t.left||!t.top?t.width||t.height||t.top||!t.left||(t.left=null):t.top=null,t},_getPaddingPlusBorderDimensions:function(t){for(var e=0,i=[],s=[t.css("borderTopWidth"),t.css("borderRightWidth"),t.css("borderBottomWidth"),t.css("borderLeftWidth")],n=[t.css("paddingTop"),t.css("paddingRight"),t.css("paddingBottom"),t.css("paddingLeft")];4>e;e++)i[e]=parseFloat(s[e])||0,i[e]+=parseFloat(n[e])||0;return{height:i[0]+i[2],width:i[1]+i[3]}},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var t,e=0,i=this.helper||this.element;this._proportionallyResizeElements.length>e;e++)t=this._proportionallyResizeElements[e],this.outerDimensions||(this.outerDimensions=this._getPaddingPlusBorderDimensions(t)),t.css({height:i.height()-this.outerDimensions.height||0,width:i.width()-this.outerDimensions.width||0})},_renderProxy:function(){var e=this.element,i=this.options;this.elementOffset=e.offset(),this._helper?(this.helper=this.helper||t("
    "),this._addClass(this.helper,this._helper),this.helper.css({width:this.element.outerWidth(),height:this.element.outerHeight(),position:"absolute",left:this.elementOffset.left+"px",top:this.elementOffset.top+"px",zIndex:++i.zIndex}),this.helper.appendTo("body").disableSelection()):this.helper=this.element},_change:{e:function(t,e){return{width:this.originalSize.width+e}},w:function(t,e){var i=this.originalSize,s=this.originalPosition;return{left:s.left+e,width:i.width-e}},n:function(t,e,i){var s=this.originalSize,n=this.originalPosition;return{top:n.top+i,height:s.height-i}},s:function(t,e,i){return{height:this.originalSize.height+i}},se:function(e,i,s){return t.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[e,i,s]))},sw:function(e,i,s){return t.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[e,i,s]))},ne:function(e,i,s){return t.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[e,i,s]))},nw:function(e,i,s){return t.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[e,i,s]))}},_propagate:function(e,i){t.ui.plugin.call(this,e,[i,this.ui()]),"resize"!==e&&this._trigger(e,i,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),t.ui.plugin.add("resizable","animate",{stop:function(e){var i=t(this).resizable("instance"),s=i.options,n=i._proportionallyResizeElements,o=n.length&&/textarea/i.test(n[0].nodeName),a=o&&i._hasScroll(n[0],"left")?0:i.sizeDiff.height,r=o?0:i.sizeDiff.width,h={width:i.size.width-r,height:i.size.height-a},l=parseFloat(i.element.css("left"))+(i.position.left-i.originalPosition.left)||null,c=parseFloat(i.element.css("top"))+(i.position.top-i.originalPosition.top)||null;i.element.animate(t.extend(h,c&&l?{top:c,left:l}:{}),{duration:s.animateDuration,easing:s.animateEasing,step:function(){var s={width:parseFloat(i.element.css("width")),height:parseFloat(i.element.css("height")),top:parseFloat(i.element.css("top")),left:parseFloat(i.element.css("left"))};n&&n.length&&t(n[0]).css({width:s.width,height:s.height}),i._updateCache(s),i._propagate("resize",e)}})}}),t.ui.plugin.add("resizable","containment",{start:function(){var e,i,s,n,o,a,r,h=t(this).resizable("instance"),l=h.options,c=h.element,u=l.containment,d=u instanceof t?u.get(0):/parent/.test(u)?c.parent().get(0):u;d&&(h.containerElement=t(d),/document/.test(u)||u===document?(h.containerOffset={left:0,top:0},h.containerPosition={left:0,top:0},h.parentData={element:t(document),left:0,top:0,width:t(document).width(),height:t(document).height()||document.body.parentNode.scrollHeight}):(e=t(d),i=[],t(["Top","Right","Left","Bottom"]).each(function(t,s){i[t]=h._num(e.css("padding"+s))}),h.containerOffset=e.offset(),h.containerPosition=e.position(),h.containerSize={height:e.innerHeight()-i[3],width:e.innerWidth()-i[1]},s=h.containerOffset,n=h.containerSize.height,o=h.containerSize.width,a=h._hasScroll(d,"left")?d.scrollWidth:o,r=h._hasScroll(d)?d.scrollHeight:n,h.parentData={element:d,left:s.left,top:s.top,width:a,height:r}))},resize:function(e){var i,s,n,o,a=t(this).resizable("instance"),r=a.options,h=a.containerOffset,l=a.position,c=a._aspectRatio||e.shiftKey,u={top:0,left:0},d=a.containerElement,p=!0;d[0]!==document&&/static/.test(d.css("position"))&&(u=h),l.left<(a._helper?h.left:0)&&(a.size.width=a.size.width+(a._helper?a.position.left-h.left:a.position.left-u.left),c&&(a.size.height=a.size.width/a.aspectRatio,p=!1),a.position.left=r.helper?h.left:0),l.top<(a._helper?h.top:0)&&(a.size.height=a.size.height+(a._helper?a.position.top-h.top:a.position.top),c&&(a.size.width=a.size.height*a.aspectRatio,p=!1),a.position.top=a._helper?h.top:0),n=a.containerElement.get(0)===a.element.parent().get(0),o=/relative|absolute/.test(a.containerElement.css("position")),n&&o?(a.offset.left=a.parentData.left+a.position.left,a.offset.top=a.parentData.top+a.position.top):(a.offset.left=a.element.offset().left,a.offset.top=a.element.offset().top),i=Math.abs(a.sizeDiff.width+(a._helper?a.offset.left-u.left:a.offset.left-h.left)),s=Math.abs(a.sizeDiff.height+(a._helper?a.offset.top-u.top:a.offset.top-h.top)),i+a.size.width>=a.parentData.width&&(a.size.width=a.parentData.width-i,c&&(a.size.height=a.size.width/a.aspectRatio,p=!1)),s+a.size.height>=a.parentData.height&&(a.size.height=a.parentData.height-s,c&&(a.size.width=a.size.height*a.aspectRatio,p=!1)),p||(a.position.left=a.prevPosition.left,a.position.top=a.prevPosition.top,a.size.width=a.prevSize.width,a.size.height=a.prevSize.height)},stop:function(){var e=t(this).resizable("instance"),i=e.options,s=e.containerOffset,n=e.containerPosition,o=e.containerElement,a=t(e.helper),r=a.offset(),h=a.outerWidth()-e.sizeDiff.width,l=a.outerHeight()-e.sizeDiff.height;e._helper&&!i.animate&&/relative/.test(o.css("position"))&&t(this).css({left:r.left-n.left-s.left,width:h,height:l}),e._helper&&!i.animate&&/static/.test(o.css("position"))&&t(this).css({left:r.left-n.left-s.left,width:h,height:l})}}),t.ui.plugin.add("resizable","alsoResize",{start:function(){var e=t(this).resizable("instance"),i=e.options;t(i.alsoResize).each(function(){var e=t(this);e.data("ui-resizable-alsoresize",{width:parseFloat(e.width()),height:parseFloat(e.height()),left:parseFloat(e.css("left")),top:parseFloat(e.css("top"))})})},resize:function(e,i){var s=t(this).resizable("instance"),n=s.options,o=s.originalSize,a=s.originalPosition,r={height:s.size.height-o.height||0,width:s.size.width-o.width||0,top:s.position.top-a.top||0,left:s.position.left-a.left||0};t(n.alsoResize).each(function(){var e=t(this),s=t(this).data("ui-resizable-alsoresize"),n={},o=e.parents(i.originalElement[0]).length?["width","height"]:["width","height","top","left"];t.each(o,function(t,e){var i=(s[e]||0)+(r[e]||0);i&&i>=0&&(n[e]=i||null)}),e.css(n)})},stop:function(){t(this).removeData("ui-resizable-alsoresize")}}),t.ui.plugin.add("resizable","ghost",{start:function(){var e=t(this).resizable("instance"),i=e.size;e.ghost=e.originalElement.clone(),e.ghost.css({opacity:.25,display:"block",position:"relative",height:i.height,width:i.width,margin:0,left:0,top:0}),e._addClass(e.ghost,"ui-resizable-ghost"),t.uiBackCompat!==!1&&"string"==typeof e.options.ghost&&e.ghost.addClass(this.options.ghost),e.ghost.appendTo(e.helper)},resize:function(){var e=t(this).resizable("instance");e.ghost&&e.ghost.css({position:"relative",height:e.size.height,width:e.size.width})},stop:function(){var e=t(this).resizable("instance");e.ghost&&e.helper&&e.helper.get(0).removeChild(e.ghost.get(0))}}),t.ui.plugin.add("resizable","grid",{resize:function(){var e,i=t(this).resizable("instance"),s=i.options,n=i.size,o=i.originalSize,a=i.originalPosition,r=i.axis,h="number"==typeof s.grid?[s.grid,s.grid]:s.grid,l=h[0]||1,c=h[1]||1,u=Math.round((n.width-o.width)/l)*l,d=Math.round((n.height-o.height)/c)*c,p=o.width+u,f=o.height+d,g=s.maxWidth&&p>s.maxWidth,m=s.maxHeight&&f>s.maxHeight,_=s.minWidth&&s.minWidth>p,v=s.minHeight&&s.minHeight>f;s.grid=h,_&&(p+=l),v&&(f+=c),g&&(p-=l),m&&(f-=c),/^(se|s|e)$/.test(r)?(i.size.width=p,i.size.height=f):/^(ne)$/.test(r)?(i.size.width=p,i.size.height=f,i.position.top=a.top-d):/^(sw)$/.test(r)?(i.size.width=p,i.size.height=f,i.position.left=a.left-u):((0>=f-c||0>=p-l)&&(e=i._getPaddingPlusBorderDimensions(this)),f-c>0?(i.size.height=f,i.position.top=a.top-d):(f=c-e.height,i.size.height=f,i.position.top=a.top+o.height-f),p-l>0?(i.size.width=p,i.position.left=a.left-u):(p=l-e.width,i.size.width=p,i.position.left=a.left+o.width-p))}}),t.ui.resizable,t.widget("ui.dialog",{version:"1.12.1",options:{appendTo:"body",autoOpen:!0,buttons:[],classes:{"ui-dialog":"ui-corner-all","ui-dialog-titlebar":"ui-corner-all"},closeOnEscape:!0,closeText:"Close",draggable:!0,hide:null,height:"auto",maxHeight:null,maxWidth:null,minHeight:150,minWidth:150,modal:!1,position:{my:"center",at:"center",of:window,collision:"fit",using:function(e){var i=t(this).css(e).offset().top;0>i&&t(this).css("top",e.top-i)}},resizable:!0,show:null,title:null,width:300,beforeClose:null,close:null,drag:null,dragStart:null,dragStop:null,focus:null,open:null,resize:null,resizeStart:null,resizeStop:null},sizeRelatedOptions:{buttons:!0,height:!0,maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0,width:!0},resizableRelatedOptions:{maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0},_create:function(){this.originalCss={display:this.element[0].style.display,width:this.element[0].style.width,minHeight:this.element[0].style.minHeight,maxHeight:this.element[0].style.maxHeight,height:this.element[0].style.height},this.originalPosition={parent:this.element.parent(),index:this.element.parent().children().index(this.element)},this.originalTitle=this.element.attr("title"),null==this.options.title&&null!=this.originalTitle&&(this.options.title=this.originalTitle),this.options.disabled&&(this.options.disabled=!1),this._createWrapper(),this.element.show().removeAttr("title").appendTo(this.uiDialog),this._addClass("ui-dialog-content","ui-widget-content"),this._createTitlebar(),this._createButtonPane(),this.options.draggable&&t.fn.draggable&&this._makeDraggable(),this.options.resizable&&t.fn.resizable&&this._makeResizable(),this._isOpen=!1,this._trackFocus()},_init:function(){this.options.autoOpen&&this.open()},_appendTo:function(){var e=this.options.appendTo;return e&&(e.jquery||e.nodeType)?t(e):this.document.find(e||"body").eq(0)},_destroy:function(){var t,e=this.originalPosition;this._untrackInstance(),this._destroyOverlay(),this.element.removeUniqueId().css(this.originalCss).detach(),this.uiDialog.remove(),this.originalTitle&&this.element.attr("title",this.originalTitle),t=e.parent.children().eq(e.index),t.length&&t[0]!==this.element[0]?t.before(this.element):e.parent.append(this.element)},widget:function(){return this.uiDialog +},disable:t.noop,enable:t.noop,close:function(e){var i=this;this._isOpen&&this._trigger("beforeClose",e)!==!1&&(this._isOpen=!1,this._focusedElement=null,this._destroyOverlay(),this._untrackInstance(),this.opener.filter(":focusable").trigger("focus").length||t.ui.safeBlur(t.ui.safeActiveElement(this.document[0])),this._hide(this.uiDialog,this.options.hide,function(){i._trigger("close",e)}))},isOpen:function(){return this._isOpen},moveToTop:function(){this._moveToTop()},_moveToTop:function(e,i){var s=!1,n=this.uiDialog.siblings(".ui-front:visible").map(function(){return+t(this).css("z-index")}).get(),o=Math.max.apply(null,n);return o>=+this.uiDialog.css("z-index")&&(this.uiDialog.css("z-index",o+1),s=!0),s&&!i&&this._trigger("focus",e),s},open:function(){var e=this;return this._isOpen?(this._moveToTop()&&this._focusTabbable(),void 0):(this._isOpen=!0,this.opener=t(t.ui.safeActiveElement(this.document[0])),this._size(),this._position(),this._createOverlay(),this._moveToTop(null,!0),this.overlay&&this.overlay.css("z-index",this.uiDialog.css("z-index")-1),this._show(this.uiDialog,this.options.show,function(){e._focusTabbable(),e._trigger("focus")}),this._makeFocusTarget(),this._trigger("open"),void 0)},_focusTabbable:function(){var t=this._focusedElement;t||(t=this.element.find("[autofocus]")),t.length||(t=this.element.find(":tabbable")),t.length||(t=this.uiDialogButtonPane.find(":tabbable")),t.length||(t=this.uiDialogTitlebarClose.filter(":tabbable")),t.length||(t=this.uiDialog),t.eq(0).trigger("focus")},_keepFocus:function(e){function i(){var e=t.ui.safeActiveElement(this.document[0]),i=this.uiDialog[0]===e||t.contains(this.uiDialog[0],e);i||this._focusTabbable()}e.preventDefault(),i.call(this),this._delay(i)},_createWrapper:function(){this.uiDialog=t("
    ").hide().attr({tabIndex:-1,role:"dialog"}).appendTo(this._appendTo()),this._addClass(this.uiDialog,"ui-dialog","ui-widget ui-widget-content ui-front"),this._on(this.uiDialog,{keydown:function(e){if(this.options.closeOnEscape&&!e.isDefaultPrevented()&&e.keyCode&&e.keyCode===t.ui.keyCode.ESCAPE)return e.preventDefault(),this.close(e),void 0;if(e.keyCode===t.ui.keyCode.TAB&&!e.isDefaultPrevented()){var i=this.uiDialog.find(":tabbable"),s=i.filter(":first"),n=i.filter(":last");e.target!==n[0]&&e.target!==this.uiDialog[0]||e.shiftKey?e.target!==s[0]&&e.target!==this.uiDialog[0]||!e.shiftKey||(this._delay(function(){n.trigger("focus")}),e.preventDefault()):(this._delay(function(){s.trigger("focus")}),e.preventDefault())}},mousedown:function(t){this._moveToTop(t)&&this._focusTabbable()}}),this.element.find("[aria-describedby]").length||this.uiDialog.attr({"aria-describedby":this.element.uniqueId().attr("id")})},_createTitlebar:function(){var e;this.uiDialogTitlebar=t("
    "),this._addClass(this.uiDialogTitlebar,"ui-dialog-titlebar","ui-widget-header ui-helper-clearfix"),this._on(this.uiDialogTitlebar,{mousedown:function(e){t(e.target).closest(".ui-dialog-titlebar-close")||this.uiDialog.trigger("focus")}}),this.uiDialogTitlebarClose=t("").button({label:t("").text(this.options.closeText).html(),icon:"ui-icon-closethick",showLabel:!1}).appendTo(this.uiDialogTitlebar),this._addClass(this.uiDialogTitlebarClose,"ui-dialog-titlebar-close"),this._on(this.uiDialogTitlebarClose,{click:function(t){t.preventDefault(),this.close(t)}}),e=t("").uniqueId().prependTo(this.uiDialogTitlebar),this._addClass(e,"ui-dialog-title"),this._title(e),this.uiDialogTitlebar.prependTo(this.uiDialog),this.uiDialog.attr({"aria-labelledby":e.attr("id")})},_title:function(t){this.options.title?t.text(this.options.title):t.html(" ")},_createButtonPane:function(){this.uiDialogButtonPane=t("
    "),this._addClass(this.uiDialogButtonPane,"ui-dialog-buttonpane","ui-widget-content ui-helper-clearfix"),this.uiButtonSet=t("
    ").appendTo(this.uiDialogButtonPane),this._addClass(this.uiButtonSet,"ui-dialog-buttonset"),this._createButtons()},_createButtons:function(){var e=this,i=this.options.buttons;return this.uiDialogButtonPane.remove(),this.uiButtonSet.empty(),t.isEmptyObject(i)||t.isArray(i)&&!i.length?(this._removeClass(this.uiDialog,"ui-dialog-buttons"),void 0):(t.each(i,function(i,s){var n,o;s=t.isFunction(s)?{click:s,text:i}:s,s=t.extend({type:"button"},s),n=s.click,o={icon:s.icon,iconPosition:s.iconPosition,showLabel:s.showLabel,icons:s.icons,text:s.text},delete s.click,delete s.icon,delete s.iconPosition,delete s.showLabel,delete s.icons,"boolean"==typeof s.text&&delete s.text,t("",s).button(o).appendTo(e.uiButtonSet).on("click",function(){n.apply(e.element[0],arguments)})}),this._addClass(this.uiDialog,"ui-dialog-buttons"),this.uiDialogButtonPane.appendTo(this.uiDialog),void 0)},_makeDraggable:function(){function e(t){return{position:t.position,offset:t.offset}}var i=this,s=this.options;this.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(s,n){i._addClass(t(this),"ui-dialog-dragging"),i._blockFrames(),i._trigger("dragStart",s,e(n))},drag:function(t,s){i._trigger("drag",t,e(s))},stop:function(n,o){var a=o.offset.left-i.document.scrollLeft(),r=o.offset.top-i.document.scrollTop();s.position={my:"left top",at:"left"+(a>=0?"+":"")+a+" "+"top"+(r>=0?"+":"")+r,of:i.window},i._removeClass(t(this),"ui-dialog-dragging"),i._unblockFrames(),i._trigger("dragStop",n,e(o))}})},_makeResizable:function(){function e(t){return{originalPosition:t.originalPosition,originalSize:t.originalSize,position:t.position,size:t.size}}var i=this,s=this.options,n=s.resizable,o=this.uiDialog.css("position"),a="string"==typeof n?n:"n,e,s,w,se,sw,ne,nw";this.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:this.element,maxWidth:s.maxWidth,maxHeight:s.maxHeight,minWidth:s.minWidth,minHeight:this._minHeight(),handles:a,start:function(s,n){i._addClass(t(this),"ui-dialog-resizing"),i._blockFrames(),i._trigger("resizeStart",s,e(n))},resize:function(t,s){i._trigger("resize",t,e(s))},stop:function(n,o){var a=i.uiDialog.offset(),r=a.left-i.document.scrollLeft(),h=a.top-i.document.scrollTop();s.height=i.uiDialog.height(),s.width=i.uiDialog.width(),s.position={my:"left top",at:"left"+(r>=0?"+":"")+r+" "+"top"+(h>=0?"+":"")+h,of:i.window},i._removeClass(t(this),"ui-dialog-resizing"),i._unblockFrames(),i._trigger("resizeStop",n,e(o))}}).css("position",o)},_trackFocus:function(){this._on(this.widget(),{focusin:function(e){this._makeFocusTarget(),this._focusedElement=t(e.target)}})},_makeFocusTarget:function(){this._untrackInstance(),this._trackingInstances().unshift(this)},_untrackInstance:function(){var e=this._trackingInstances(),i=t.inArray(this,e);-1!==i&&e.splice(i,1)},_trackingInstances:function(){var t=this.document.data("ui-dialog-instances");return t||(t=[],this.document.data("ui-dialog-instances",t)),t},_minHeight:function(){var t=this.options;return"auto"===t.height?t.minHeight:Math.min(t.minHeight,t.height)},_position:function(){var t=this.uiDialog.is(":visible");t||this.uiDialog.show(),this.uiDialog.position(this.options.position),t||this.uiDialog.hide()},_setOptions:function(e){var i=this,s=!1,n={};t.each(e,function(t,e){i._setOption(t,e),t in i.sizeRelatedOptions&&(s=!0),t in i.resizableRelatedOptions&&(n[t]=e)}),s&&(this._size(),this._position()),this.uiDialog.is(":data(ui-resizable)")&&this.uiDialog.resizable("option",n)},_setOption:function(e,i){var s,n,o=this.uiDialog;"disabled"!==e&&(this._super(e,i),"appendTo"===e&&this.uiDialog.appendTo(this._appendTo()),"buttons"===e&&this._createButtons(),"closeText"===e&&this.uiDialogTitlebarClose.button({label:t("").text(""+this.options.closeText).html()}),"draggable"===e&&(s=o.is(":data(ui-draggable)"),s&&!i&&o.draggable("destroy"),!s&&i&&this._makeDraggable()),"position"===e&&this._position(),"resizable"===e&&(n=o.is(":data(ui-resizable)"),n&&!i&&o.resizable("destroy"),n&&"string"==typeof i&&o.resizable("option","handles",i),n||i===!1||this._makeResizable()),"title"===e&&this._title(this.uiDialogTitlebar.find(".ui-dialog-title")))},_size:function(){var t,e,i,s=this.options;this.element.show().css({width:"auto",minHeight:0,maxHeight:"none",height:0}),s.minWidth>s.width&&(s.width=s.minWidth),t=this.uiDialog.css({height:"auto",width:s.width}).outerHeight(),e=Math.max(0,s.minHeight-t),i="number"==typeof s.maxHeight?Math.max(0,s.maxHeight-t):"none","auto"===s.height?this.element.css({minHeight:e,maxHeight:i,height:"auto"}):this.element.height(Math.max(0,s.height-t)),this.uiDialog.is(":data(ui-resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())},_blockFrames:function(){this.iframeBlocks=this.document.find("iframe").map(function(){var e=t(this);return t("
    ").css({position:"absolute",width:e.outerWidth(),height:e.outerHeight()}).appendTo(e.parent()).offset(e.offset())[0]})},_unblockFrames:function(){this.iframeBlocks&&(this.iframeBlocks.remove(),delete this.iframeBlocks)},_allowInteraction:function(e){return t(e.target).closest(".ui-dialog").length?!0:!!t(e.target).closest(".ui-datepicker").length},_createOverlay:function(){if(this.options.modal){var e=!0;this._delay(function(){e=!1}),this.document.data("ui-dialog-overlays")||this._on(this.document,{focusin:function(t){e||this._allowInteraction(t)||(t.preventDefault(),this._trackingInstances()[0]._focusTabbable())}}),this.overlay=t("
    ").appendTo(this._appendTo()),this._addClass(this.overlay,null,"ui-widget-overlay ui-front"),this._on(this.overlay,{mousedown:"_keepFocus"}),this.document.data("ui-dialog-overlays",(this.document.data("ui-dialog-overlays")||0)+1)}},_destroyOverlay:function(){if(this.options.modal&&this.overlay){var t=this.document.data("ui-dialog-overlays")-1;t?this.document.data("ui-dialog-overlays",t):(this._off(this.document,"focusin"),this.document.removeData("ui-dialog-overlays")),this.overlay.remove(),this.overlay=null}}}),t.uiBackCompat!==!1&&t.widget("ui.dialog",t.ui.dialog,{options:{dialogClass:""},_createWrapper:function(){this._super(),this.uiDialog.addClass(this.options.dialogClass)},_setOption:function(t,e){"dialogClass"===t&&this.uiDialog.removeClass(this.options.dialogClass).addClass(e),this._superApply(arguments)}}),t.ui.dialog,t.widget("ui.droppable",{version:"1.12.1",widgetEventPrefix:"drop",options:{accept:"*",addClasses:!0,greedy:!1,scope:"default",tolerance:"intersect",activate:null,deactivate:null,drop:null,out:null,over:null},_create:function(){var e,i=this.options,s=i.accept;this.isover=!1,this.isout=!0,this.accept=t.isFunction(s)?s:function(t){return t.is(s)},this.proportions=function(){return arguments.length?(e=arguments[0],void 0):e?e:e={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight}},this._addToManager(i.scope),i.addClasses&&this._addClass("ui-droppable")},_addToManager:function(e){t.ui.ddmanager.droppables[e]=t.ui.ddmanager.droppables[e]||[],t.ui.ddmanager.droppables[e].push(this)},_splice:function(t){for(var e=0;t.length>e;e++)t[e]===this&&t.splice(e,1)},_destroy:function(){var e=t.ui.ddmanager.droppables[this.options.scope];this._splice(e)},_setOption:function(e,i){if("accept"===e)this.accept=t.isFunction(i)?i:function(t){return t.is(i)};else if("scope"===e){var s=t.ui.ddmanager.droppables[this.options.scope];this._splice(s),this._addToManager(i)}this._super(e,i)},_activate:function(e){var i=t.ui.ddmanager.current;this._addActiveClass(),i&&this._trigger("activate",e,this.ui(i))},_deactivate:function(e){var i=t.ui.ddmanager.current;this._removeActiveClass(),i&&this._trigger("deactivate",e,this.ui(i))},_over:function(e){var i=t.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this._addHoverClass(),this._trigger("over",e,this.ui(i)))},_out:function(e){var i=t.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this._removeHoverClass(),this._trigger("out",e,this.ui(i)))},_drop:function(e,i){var s=i||t.ui.ddmanager.current,n=!1;return s&&(s.currentItem||s.element)[0]!==this.element[0]?(this.element.find(":data(ui-droppable)").not(".ui-draggable-dragging").each(function(){var i=t(this).droppable("instance");return i.options.greedy&&!i.options.disabled&&i.options.scope===s.options.scope&&i.accept.call(i.element[0],s.currentItem||s.element)&&v(s,t.extend(i,{offset:i.element.offset()}),i.options.tolerance,e)?(n=!0,!1):void 0}),n?!1:this.accept.call(this.element[0],s.currentItem||s.element)?(this._removeActiveClass(),this._removeHoverClass(),this._trigger("drop",e,this.ui(s)),this.element):!1):!1},ui:function(t){return{draggable:t.currentItem||t.element,helper:t.helper,position:t.position,offset:t.positionAbs}},_addHoverClass:function(){this._addClass("ui-droppable-hover")},_removeHoverClass:function(){this._removeClass("ui-droppable-hover")},_addActiveClass:function(){this._addClass("ui-droppable-active")},_removeActiveClass:function(){this._removeClass("ui-droppable-active")}});var v=t.ui.intersect=function(){function t(t,e,i){return t>=e&&e+i>t}return function(e,i,s,n){if(!i.offset)return!1;var o=(e.positionAbs||e.position.absolute).left+e.margins.left,a=(e.positionAbs||e.position.absolute).top+e.margins.top,r=o+e.helperProportions.width,h=a+e.helperProportions.height,l=i.offset.left,c=i.offset.top,u=l+i.proportions().width,d=c+i.proportions().height;switch(s){case"fit":return o>=l&&u>=r&&a>=c&&d>=h;case"intersect":return o+e.helperProportions.width/2>l&&u>r-e.helperProportions.width/2&&a+e.helperProportions.height/2>c&&d>h-e.helperProportions.height/2;case"pointer":return t(n.pageY,c,i.proportions().height)&&t(n.pageX,l,i.proportions().width);case"touch":return(a>=c&&d>=a||h>=c&&d>=h||c>a&&h>d)&&(o>=l&&u>=o||r>=l&&u>=r||l>o&&r>u);default:return!1}}}();t.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(e,i){var s,n,o=t.ui.ddmanager.droppables[e.options.scope]||[],a=i?i.type:null,r=(e.currentItem||e.element).find(":data(ui-droppable)").addBack();t:for(s=0;o.length>s;s++)if(!(o[s].options.disabled||e&&!o[s].accept.call(o[s].element[0],e.currentItem||e.element))){for(n=0;r.length>n;n++)if(r[n]===o[s].element[0]){o[s].proportions().height=0;continue t}o[s].visible="none"!==o[s].element.css("display"),o[s].visible&&("mousedown"===a&&o[s]._activate.call(o[s],i),o[s].offset=o[s].element.offset(),o[s].proportions({width:o[s].element[0].offsetWidth,height:o[s].element[0].offsetHeight}))}},drop:function(e,i){var s=!1;return t.each((t.ui.ddmanager.droppables[e.options.scope]||[]).slice(),function(){this.options&&(!this.options.disabled&&this.visible&&v(e,this,this.options.tolerance,i)&&(s=this._drop.call(this,i)||s),!this.options.disabled&&this.visible&&this.accept.call(this.element[0],e.currentItem||e.element)&&(this.isout=!0,this.isover=!1,this._deactivate.call(this,i)))}),s},dragStart:function(e,i){e.element.parentsUntil("body").on("scroll.droppable",function(){e.options.refreshPositions||t.ui.ddmanager.prepareOffsets(e,i)})},drag:function(e,i){e.options.refreshPositions&&t.ui.ddmanager.prepareOffsets(e,i),t.each(t.ui.ddmanager.droppables[e.options.scope]||[],function(){if(!this.options.disabled&&!this.greedyChild&&this.visible){var s,n,o,a=v(e,this,this.options.tolerance,i),r=!a&&this.isover?"isout":a&&!this.isover?"isover":null;r&&(this.options.greedy&&(n=this.options.scope,o=this.element.parents(":data(ui-droppable)").filter(function(){return t(this).droppable("instance").options.scope===n}),o.length&&(s=t(o[0]).droppable("instance"),s.greedyChild="isover"===r)),s&&"isover"===r&&(s.isover=!1,s.isout=!0,s._out.call(s,i)),this[r]=!0,this["isout"===r?"isover":"isout"]=!1,this["isover"===r?"_over":"_out"].call(this,i),s&&"isout"===r&&(s.isout=!1,s.isover=!0,s._over.call(s,i)))}})},dragStop:function(e,i){e.element.parentsUntil("body").off("scroll.droppable"),e.options.refreshPositions||t.ui.ddmanager.prepareOffsets(e,i)}},t.uiBackCompat!==!1&&t.widget("ui.droppable",t.ui.droppable,{options:{hoverClass:!1,activeClass:!1},_addActiveClass:function(){this._super(),this.options.activeClass&&this.element.addClass(this.options.activeClass)},_removeActiveClass:function(){this._super(),this.options.activeClass&&this.element.removeClass(this.options.activeClass)},_addHoverClass:function(){this._super(),this.options.hoverClass&&this.element.addClass(this.options.hoverClass)},_removeHoverClass:function(){this._super(),this.options.hoverClass&&this.element.removeClass(this.options.hoverClass)}}),t.ui.droppable,t.widget("ui.progressbar",{version:"1.12.1",options:{classes:{"ui-progressbar":"ui-corner-all","ui-progressbar-value":"ui-corner-left","ui-progressbar-complete":"ui-corner-right"},max:100,value:0,change:null,complete:null},min:0,_create:function(){this.oldValue=this.options.value=this._constrainedValue(),this.element.attr({role:"progressbar","aria-valuemin":this.min}),this._addClass("ui-progressbar","ui-widget ui-widget-content"),this.valueDiv=t("
    ").appendTo(this.element),this._addClass(this.valueDiv,"ui-progressbar-value","ui-widget-header"),this._refreshValue()},_destroy:function(){this.element.removeAttr("role aria-valuemin aria-valuemax aria-valuenow"),this.valueDiv.remove()},value:function(t){return void 0===t?this.options.value:(this.options.value=this._constrainedValue(t),this._refreshValue(),void 0)},_constrainedValue:function(t){return void 0===t&&(t=this.options.value),this.indeterminate=t===!1,"number"!=typeof t&&(t=0),this.indeterminate?!1:Math.min(this.options.max,Math.max(this.min,t))},_setOptions:function(t){var e=t.value;delete t.value,this._super(t),this.options.value=this._constrainedValue(e),this._refreshValue()},_setOption:function(t,e){"max"===t&&(e=Math.max(this.min,e)),this._super(t,e)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",t),this._toggleClass(null,"ui-state-disabled",!!t)},_percentage:function(){return this.indeterminate?100:100*(this.options.value-this.min)/(this.options.max-this.min)},_refreshValue:function(){var e=this.options.value,i=this._percentage();this.valueDiv.toggle(this.indeterminate||e>this.min).width(i.toFixed(0)+"%"),this._toggleClass(this.valueDiv,"ui-progressbar-complete",null,e===this.options.max)._toggleClass("ui-progressbar-indeterminate",null,this.indeterminate),this.indeterminate?(this.element.removeAttr("aria-valuenow"),this.overlayDiv||(this.overlayDiv=t("
    ").appendTo(this.valueDiv),this._addClass(this.overlayDiv,"ui-progressbar-overlay"))):(this.element.attr({"aria-valuemax":this.options.max,"aria-valuenow":e}),this.overlayDiv&&(this.overlayDiv.remove(),this.overlayDiv=null)),this.oldValue!==e&&(this.oldValue=e,this._trigger("change")),e===this.options.max&&this._trigger("complete")}}),t.widget("ui.selectable",t.ui.mouse,{version:"1.12.1",options:{appendTo:"body",autoRefresh:!0,distance:0,filter:"*",tolerance:"touch",selected:null,selecting:null,start:null,stop:null,unselected:null,unselecting:null},_create:function(){var e=this;this._addClass("ui-selectable"),this.dragged=!1,this.refresh=function(){e.elementPos=t(e.element[0]).offset(),e.selectees=t(e.options.filter,e.element[0]),e._addClass(e.selectees,"ui-selectee"),e.selectees.each(function(){var i=t(this),s=i.offset(),n={left:s.left-e.elementPos.left,top:s.top-e.elementPos.top};t.data(this,"selectable-item",{element:this,$element:i,left:n.left,top:n.top,right:n.left+i.outerWidth(),bottom:n.top+i.outerHeight(),startselected:!1,selected:i.hasClass("ui-selected"),selecting:i.hasClass("ui-selecting"),unselecting:i.hasClass("ui-unselecting")})})},this.refresh(),this._mouseInit(),this.helper=t("
    "),this._addClass(this.helper,"ui-selectable-helper")},_destroy:function(){this.selectees.removeData("selectable-item"),this._mouseDestroy()},_mouseStart:function(e){var i=this,s=this.options;this.opos=[e.pageX,e.pageY],this.elementPos=t(this.element[0]).offset(),this.options.disabled||(this.selectees=t(s.filter,this.element[0]),this._trigger("start",e),t(s.appendTo).append(this.helper),this.helper.css({left:e.pageX,top:e.pageY,width:0,height:0}),s.autoRefresh&&this.refresh(),this.selectees.filter(".ui-selected").each(function(){var s=t.data(this,"selectable-item");s.startselected=!0,e.metaKey||e.ctrlKey||(i._removeClass(s.$element,"ui-selected"),s.selected=!1,i._addClass(s.$element,"ui-unselecting"),s.unselecting=!0,i._trigger("unselecting",e,{unselecting:s.element}))}),t(e.target).parents().addBack().each(function(){var s,n=t.data(this,"selectable-item");return n?(s=!e.metaKey&&!e.ctrlKey||!n.$element.hasClass("ui-selected"),i._removeClass(n.$element,s?"ui-unselecting":"ui-selected")._addClass(n.$element,s?"ui-selecting":"ui-unselecting"),n.unselecting=!s,n.selecting=s,n.selected=s,s?i._trigger("selecting",e,{selecting:n.element}):i._trigger("unselecting",e,{unselecting:n.element}),!1):void 0}))},_mouseDrag:function(e){if(this.dragged=!0,!this.options.disabled){var i,s=this,n=this.options,o=this.opos[0],a=this.opos[1],r=e.pageX,h=e.pageY;return o>r&&(i=r,r=o,o=i),a>h&&(i=h,h=a,a=i),this.helper.css({left:o,top:a,width:r-o,height:h-a}),this.selectees.each(function(){var i=t.data(this,"selectable-item"),l=!1,c={};i&&i.element!==s.element[0]&&(c.left=i.left+s.elementPos.left,c.right=i.right+s.elementPos.left,c.top=i.top+s.elementPos.top,c.bottom=i.bottom+s.elementPos.top,"touch"===n.tolerance?l=!(c.left>r||o>c.right||c.top>h||a>c.bottom):"fit"===n.tolerance&&(l=c.left>o&&r>c.right&&c.top>a&&h>c.bottom),l?(i.selected&&(s._removeClass(i.$element,"ui-selected"),i.selected=!1),i.unselecting&&(s._removeClass(i.$element,"ui-unselecting"),i.unselecting=!1),i.selecting||(s._addClass(i.$element,"ui-selecting"),i.selecting=!0,s._trigger("selecting",e,{selecting:i.element}))):(i.selecting&&((e.metaKey||e.ctrlKey)&&i.startselected?(s._removeClass(i.$element,"ui-selecting"),i.selecting=!1,s._addClass(i.$element,"ui-selected"),i.selected=!0):(s._removeClass(i.$element,"ui-selecting"),i.selecting=!1,i.startselected&&(s._addClass(i.$element,"ui-unselecting"),i.unselecting=!0),s._trigger("unselecting",e,{unselecting:i.element}))),i.selected&&(e.metaKey||e.ctrlKey||i.startselected||(s._removeClass(i.$element,"ui-selected"),i.selected=!1,s._addClass(i.$element,"ui-unselecting"),i.unselecting=!0,s._trigger("unselecting",e,{unselecting:i.element})))))}),!1}},_mouseStop:function(e){var i=this;return this.dragged=!1,t(".ui-unselecting",this.element[0]).each(function(){var s=t.data(this,"selectable-item");i._removeClass(s.$element,"ui-unselecting"),s.unselecting=!1,s.startselected=!1,i._trigger("unselected",e,{unselected:s.element})}),t(".ui-selecting",this.element[0]).each(function(){var s=t.data(this,"selectable-item");i._removeClass(s.$element,"ui-selecting")._addClass(s.$element,"ui-selected"),s.selecting=!1,s.selected=!0,s.startselected=!0,i._trigger("selected",e,{selected:s.element})}),this._trigger("stop",e),this.helper.remove(),!1}}),t.widget("ui.selectmenu",[t.ui.formResetMixin,{version:"1.12.1",defaultElement:"",widgetEventPrefix:"spin",options:{classes:{"ui-spinner":"ui-corner-all","ui-spinner-down":"ui-corner-br","ui-spinner-up":"ui-corner-tr"},culture:null,icons:{down:"ui-icon-triangle-1-s",up:"ui-icon-triangle-1-n"},incremental:!0,max:null,min:null,numberFormat:null,page:10,step:1,change:null,spin:null,start:null,stop:null},_create:function(){this._setOption("max",this.options.max),this._setOption("min",this.options.min),this._setOption("step",this.options.step),""!==this.value()&&this._value(this.element.val(),!0),this._draw(),this._on(this._events),this._refresh(),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_getCreateOptions:function(){var e=this._super(),i=this.element;return t.each(["min","max","step"],function(t,s){var n=i.attr(s);null!=n&&n.length&&(e[s]=n)}),e},_events:{keydown:function(t){this._start(t)&&this._keydown(t)&&t.preventDefault()},keyup:"_stop",focus:function(){this.previous=this.element.val()},blur:function(t){return this.cancelBlur?(delete this.cancelBlur,void 0):(this._stop(),this._refresh(),this.previous!==this.element.val()&&this._trigger("change",t),void 0)},mousewheel:function(t,e){if(e){if(!this.spinning&&!this._start(t))return!1;this._spin((e>0?1:-1)*this.options.step,t),clearTimeout(this.mousewheelTimer),this.mousewheelTimer=this._delay(function(){this.spinning&&this._stop(t)},100),t.preventDefault()}},"mousedown .ui-spinner-button":function(e){function i(){var e=this.element[0]===t.ui.safeActiveElement(this.document[0]);e||(this.element.trigger("focus"),this.previous=s,this._delay(function(){this.previous=s}))}var s;s=this.element[0]===t.ui.safeActiveElement(this.document[0])?this.previous:this.element.val(),e.preventDefault(),i.call(this),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur,i.call(this)}),this._start(e)!==!1&&this._repeat(null,t(e.currentTarget).hasClass("ui-spinner-up")?1:-1,e)},"mouseup .ui-spinner-button":"_stop","mouseenter .ui-spinner-button":function(e){return t(e.currentTarget).hasClass("ui-state-active")?this._start(e)===!1?!1:(this._repeat(null,t(e.currentTarget).hasClass("ui-spinner-up")?1:-1,e),void 0):void 0},"mouseleave .ui-spinner-button":"_stop"},_enhance:function(){this.uiSpinner=this.element.attr("autocomplete","off").wrap("").parent().append("")},_draw:function(){this._enhance(),this._addClass(this.uiSpinner,"ui-spinner","ui-widget ui-widget-content"),this._addClass("ui-spinner-input"),this.element.attr("role","spinbutton"),this.buttons=this.uiSpinner.children("a").attr("tabIndex",-1).attr("aria-hidden",!0).button({classes:{"ui-button":""}}),this._removeClass(this.buttons,"ui-corner-all"),this._addClass(this.buttons.first(),"ui-spinner-button ui-spinner-up"),this._addClass(this.buttons.last(),"ui-spinner-button ui-spinner-down"),this.buttons.first().button({icon:this.options.icons.up,showLabel:!1}),this.buttons.last().button({icon:this.options.icons.down,showLabel:!1}),this.buttons.height()>Math.ceil(.5*this.uiSpinner.height())&&this.uiSpinner.height()>0&&this.uiSpinner.height(this.uiSpinner.height())},_keydown:function(e){var i=this.options,s=t.ui.keyCode;switch(e.keyCode){case s.UP:return this._repeat(null,1,e),!0;case s.DOWN:return this._repeat(null,-1,e),!0;case s.PAGE_UP:return this._repeat(null,i.page,e),!0;case s.PAGE_DOWN:return this._repeat(null,-i.page,e),!0}return!1},_start:function(t){return this.spinning||this._trigger("start",t)!==!1?(this.counter||(this.counter=1),this.spinning=!0,!0):!1},_repeat:function(t,e,i){t=t||500,clearTimeout(this.timer),this.timer=this._delay(function(){this._repeat(40,e,i)},t),this._spin(e*this.options.step,i)},_spin:function(t,e){var i=this.value()||0;this.counter||(this.counter=1),i=this._adjustValue(i+t*this._increment(this.counter)),this.spinning&&this._trigger("spin",e,{value:i})===!1||(this._value(i),this.counter++)},_increment:function(e){var i=this.options.incremental;return i?t.isFunction(i)?i(e):Math.floor(e*e*e/5e4-e*e/500+17*e/200+1):1},_precision:function(){var t=this._precisionOf(this.options.step);return null!==this.options.min&&(t=Math.max(t,this._precisionOf(this.options.min))),t},_precisionOf:function(t){var e=""+t,i=e.indexOf(".");return-1===i?0:e.length-i-1},_adjustValue:function(t){var e,i,s=this.options;return e=null!==s.min?s.min:0,i=t-e,i=Math.round(i/s.step)*s.step,t=e+i,t=parseFloat(t.toFixed(this._precision())),null!==s.max&&t>s.max?s.max:null!==s.min&&s.min>t?s.min:t},_stop:function(t){this.spinning&&(clearTimeout(this.timer),clearTimeout(this.mousewheelTimer),this.counter=0,this.spinning=!1,this._trigger("stop",t))},_setOption:function(t,e){var i,s,n;return"culture"===t||"numberFormat"===t?(i=this._parse(this.element.val()),this.options[t]=e,this.element.val(this._format(i)),void 0):(("max"===t||"min"===t||"step"===t)&&"string"==typeof e&&(e=this._parse(e)),"icons"===t&&(s=this.buttons.first().find(".ui-icon"),this._removeClass(s,null,this.options.icons.up),this._addClass(s,null,e.up),n=this.buttons.last().find(".ui-icon"),this._removeClass(n,null,this.options.icons.down),this._addClass(n,null,e.down)),this._super(t,e),void 0)},_setOptionDisabled:function(t){this._super(t),this._toggleClass(this.uiSpinner,null,"ui-state-disabled",!!t),this.element.prop("disabled",!!t),this.buttons.button(t?"disable":"enable")},_setOptions:r(function(t){this._super(t)}),_parse:function(t){return"string"==typeof t&&""!==t&&(t=window.Globalize&&this.options.numberFormat?Globalize.parseFloat(t,10,this.options.culture):+t),""===t||isNaN(t)?null:t},_format:function(t){return""===t?"":window.Globalize&&this.options.numberFormat?Globalize.format(t,this.options.numberFormat,this.options.culture):t},_refresh:function(){this.element.attr({"aria-valuemin":this.options.min,"aria-valuemax":this.options.max,"aria-valuenow":this._parse(this.element.val())})},isValid:function(){var t=this.value();return null===t?!1:t===this._adjustValue(t)},_value:function(t,e){var i;""!==t&&(i=this._parse(t),null!==i&&(e||(i=this._adjustValue(i)),t=this._format(i))),this.element.val(t),this._refresh()},_destroy:function(){this.element.prop("disabled",!1).removeAttr("autocomplete role aria-valuemin aria-valuemax aria-valuenow"),this.uiSpinner.replaceWith(this.element)},stepUp:r(function(t){this._stepUp(t)}),_stepUp:function(t){this._start()&&(this._spin((t||1)*this.options.step),this._stop())},stepDown:r(function(t){this._stepDown(t)}),_stepDown:function(t){this._start()&&(this._spin((t||1)*-this.options.step),this._stop())},pageUp:r(function(t){this._stepUp((t||1)*this.options.page)}),pageDown:r(function(t){this._stepDown((t||1)*this.options.page)}),value:function(t){return arguments.length?(r(this._value).call(this,t),void 0):this._parse(this.element.val())},widget:function(){return this.uiSpinner}}),t.uiBackCompat!==!1&&t.widget("ui.spinner",t.ui.spinner,{_enhance:function(){this.uiSpinner=this.element.attr("autocomplete","off").wrap(this._uiSpinnerHtml()).parent().append(this._buttonHtml())},_uiSpinnerHtml:function(){return""},_buttonHtml:function(){return""}}),t.ui.spinner,t.widget("ui.tabs",{version:"1.12.1",delay:300,options:{active:null,classes:{"ui-tabs":"ui-corner-all","ui-tabs-nav":"ui-corner-all","ui-tabs-panel":"ui-corner-bottom","ui-tabs-tab":"ui-corner-top"},collapsible:!1,event:"click",heightStyle:"content",hide:null,show:null,activate:null,beforeActivate:null,beforeLoad:null,load:null},_isLocal:function(){var t=/#.*$/;return function(e){var i,s;i=e.href.replace(t,""),s=location.href.replace(t,"");try{i=decodeURIComponent(i)}catch(n){}try{s=decodeURIComponent(s)}catch(n){}return e.hash.length>1&&i===s}}(),_create:function(){var e=this,i=this.options;this.running=!1,this._addClass("ui-tabs","ui-widget ui-widget-content"),this._toggleClass("ui-tabs-collapsible",null,i.collapsible),this._processTabs(),i.active=this._initialActive(),t.isArray(i.disabled)&&(i.disabled=t.unique(i.disabled.concat(t.map(this.tabs.filter(".ui-state-disabled"),function(t){return e.tabs.index(t)}))).sort()),this.active=this.options.active!==!1&&this.anchors.length?this._findActive(i.active):t(),this._refresh(),this.active.length&&this.load(i.active)},_initialActive:function(){var e=this.options.active,i=this.options.collapsible,s=location.hash.substring(1);return null===e&&(s&&this.tabs.each(function(i,n){return t(n).attr("aria-controls")===s?(e=i,!1):void 0}),null===e&&(e=this.tabs.index(this.tabs.filter(".ui-tabs-active"))),(null===e||-1===e)&&(e=this.tabs.length?0:!1)),e!==!1&&(e=this.tabs.index(this.tabs.eq(e)),-1===e&&(e=i?!1:0)),!i&&e===!1&&this.anchors.length&&(e=0),e},_getCreateEventData:function(){return{tab:this.active,panel:this.active.length?this._getPanelForTab(this.active):t()}},_tabKeydown:function(e){var i=t(t.ui.safeActiveElement(this.document[0])).closest("li"),s=this.tabs.index(i),n=!0;if(!this._handlePageNav(e)){switch(e.keyCode){case t.ui.keyCode.RIGHT:case t.ui.keyCode.DOWN:s++;break;case t.ui.keyCode.UP:case t.ui.keyCode.LEFT:n=!1,s--;break;case t.ui.keyCode.END:s=this.anchors.length-1;break;case t.ui.keyCode.HOME:s=0;break;case t.ui.keyCode.SPACE:return e.preventDefault(),clearTimeout(this.activating),this._activate(s),void 0;case t.ui.keyCode.ENTER:return e.preventDefault(),clearTimeout(this.activating),this._activate(s===this.options.active?!1:s),void 0;default:return}e.preventDefault(),clearTimeout(this.activating),s=this._focusNextTab(s,n),e.ctrlKey||e.metaKey||(i.attr("aria-selected","false"),this.tabs.eq(s).attr("aria-selected","true"),this.activating=this._delay(function(){this.option("active",s)},this.delay))}},_panelKeydown:function(e){this._handlePageNav(e)||e.ctrlKey&&e.keyCode===t.ui.keyCode.UP&&(e.preventDefault(),this.active.trigger("focus"))},_handlePageNav:function(e){return e.altKey&&e.keyCode===t.ui.keyCode.PAGE_UP?(this._activate(this._focusNextTab(this.options.active-1,!1)),!0):e.altKey&&e.keyCode===t.ui.keyCode.PAGE_DOWN?(this._activate(this._focusNextTab(this.options.active+1,!0)),!0):void 0},_findNextTab:function(e,i){function s(){return e>n&&(e=0),0>e&&(e=n),e}for(var n=this.tabs.length-1;-1!==t.inArray(s(),this.options.disabled);)e=i?e+1:e-1;return e},_focusNextTab:function(t,e){return t=this._findNextTab(t,e),this.tabs.eq(t).trigger("focus"),t},_setOption:function(t,e){return"active"===t?(this._activate(e),void 0):(this._super(t,e),"collapsible"===t&&(this._toggleClass("ui-tabs-collapsible",null,e),e||this.options.active!==!1||this._activate(0)),"event"===t&&this._setupEvents(e),"heightStyle"===t&&this._setupHeightStyle(e),void 0)},_sanitizeSelector:function(t){return t?t.replace(/[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g,"\\$&"):""},refresh:function(){var e=this.options,i=this.tablist.children(":has(a[href])");e.disabled=t.map(i.filter(".ui-state-disabled"),function(t){return i.index(t)}),this._processTabs(),e.active!==!1&&this.anchors.length?this.active.length&&!t.contains(this.tablist[0],this.active[0])?this.tabs.length===e.disabled.length?(e.active=!1,this.active=t()):this._activate(this._findNextTab(Math.max(0,e.active-1),!1)):e.active=this.tabs.index(this.active):(e.active=!1,this.active=t()),this._refresh()},_refresh:function(){this._setOptionDisabled(this.options.disabled),this._setupEvents(this.options.event),this._setupHeightStyle(this.options.heightStyle),this.tabs.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}),this.panels.not(this._getPanelForTab(this.active)).hide().attr({"aria-hidden":"true"}),this.active.length?(this.active.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}),this._addClass(this.active,"ui-tabs-active","ui-state-active"),this._getPanelForTab(this.active).show().attr({"aria-hidden":"false"})):this.tabs.eq(0).attr("tabIndex",0)},_processTabs:function(){var e=this,i=this.tabs,s=this.anchors,n=this.panels;this.tablist=this._getList().attr("role","tablist"),this._addClass(this.tablist,"ui-tabs-nav","ui-helper-reset ui-helper-clearfix ui-widget-header"),this.tablist.on("mousedown"+this.eventNamespace,"> li",function(e){t(this).is(".ui-state-disabled")&&e.preventDefault()}).on("focus"+this.eventNamespace,".ui-tabs-anchor",function(){t(this).closest("li").is(".ui-state-disabled")&&this.blur()}),this.tabs=this.tablist.find("> li:has(a[href])").attr({role:"tab",tabIndex:-1}),this._addClass(this.tabs,"ui-tabs-tab","ui-state-default"),this.anchors=this.tabs.map(function(){return t("a",this)[0]}).attr({role:"presentation",tabIndex:-1}),this._addClass(this.anchors,"ui-tabs-anchor"),this.panels=t(),this.anchors.each(function(i,s){var n,o,a,r=t(s).uniqueId().attr("id"),h=t(s).closest("li"),l=h.attr("aria-controls");e._isLocal(s)?(n=s.hash,a=n.substring(1),o=e.element.find(e._sanitizeSelector(n))):(a=h.attr("aria-controls")||t({}).uniqueId()[0].id,n="#"+a,o=e.element.find(n),o.length||(o=e._createPanel(a),o.insertAfter(e.panels[i-1]||e.tablist)),o.attr("aria-live","polite")),o.length&&(e.panels=e.panels.add(o)),l&&h.data("ui-tabs-aria-controls",l),h.attr({"aria-controls":a,"aria-labelledby":r}),o.attr("aria-labelledby",r)}),this.panels.attr("role","tabpanel"),this._addClass(this.panels,"ui-tabs-panel","ui-widget-content"),i&&(this._off(i.not(this.tabs)),this._off(s.not(this.anchors)),this._off(n.not(this.panels)))},_getList:function(){return this.tablist||this.element.find("ol, ul").eq(0)},_createPanel:function(e){return t("
    ").attr("id",e).data("ui-tabs-destroy",!0)},_setOptionDisabled:function(e){var i,s,n;for(t.isArray(e)&&(e.length?e.length===this.anchors.length&&(e=!0):e=!1),n=0;s=this.tabs[n];n++)i=t(s),e===!0||-1!==t.inArray(n,e)?(i.attr("aria-disabled","true"),this._addClass(i,null,"ui-state-disabled")):(i.removeAttr("aria-disabled"),this._removeClass(i,null,"ui-state-disabled"));this.options.disabled=e,this._toggleClass(this.widget(),this.widgetFullName+"-disabled",null,e===!0)},_setupEvents:function(e){var i={};e&&t.each(e.split(" "),function(t,e){i[e]="_eventHandler"}),this._off(this.anchors.add(this.tabs).add(this.panels)),this._on(!0,this.anchors,{click:function(t){t.preventDefault()}}),this._on(this.anchors,i),this._on(this.tabs,{keydown:"_tabKeydown"}),this._on(this.panels,{keydown:"_panelKeydown"}),this._focusable(this.tabs),this._hoverable(this.tabs)},_setupHeightStyle:function(e){var i,s=this.element.parent();"fill"===e?(i=s.height(),i-=this.element.outerHeight()-this.element.height(),this.element.siblings(":visible").each(function(){var e=t(this),s=e.css("position");"absolute"!==s&&"fixed"!==s&&(i-=e.outerHeight(!0))}),this.element.children().not(this.panels).each(function(){i-=t(this).outerHeight(!0)}),this.panels.each(function(){t(this).height(Math.max(0,i-t(this).innerHeight()+t(this).height()))}).css("overflow","auto")):"auto"===e&&(i=0,this.panels.each(function(){i=Math.max(i,t(this).height("").height())}).height(i))},_eventHandler:function(e){var i=this.options,s=this.active,n=t(e.currentTarget),o=n.closest("li"),a=o[0]===s[0],r=a&&i.collapsible,h=r?t():this._getPanelForTab(o),l=s.length?this._getPanelForTab(s):t(),c={oldTab:s,oldPanel:l,newTab:r?t():o,newPanel:h};e.preventDefault(),o.hasClass("ui-state-disabled")||o.hasClass("ui-tabs-loading")||this.running||a&&!i.collapsible||this._trigger("beforeActivate",e,c)===!1||(i.active=r?!1:this.tabs.index(o),this.active=a?t():o,this.xhr&&this.xhr.abort(),l.length||h.length||t.error("jQuery UI Tabs: Mismatching fragment identifier."),h.length&&this.load(this.tabs.index(o),e),this._toggle(e,c))},_toggle:function(e,i){function s(){o.running=!1,o._trigger("activate",e,i)}function n(){o._addClass(i.newTab.closest("li"),"ui-tabs-active","ui-state-active"),a.length&&o.options.show?o._show(a,o.options.show,s):(a.show(),s())}var o=this,a=i.newPanel,r=i.oldPanel;this.running=!0,r.length&&this.options.hide?this._hide(r,this.options.hide,function(){o._removeClass(i.oldTab.closest("li"),"ui-tabs-active","ui-state-active"),n()}):(this._removeClass(i.oldTab.closest("li"),"ui-tabs-active","ui-state-active"),r.hide(),n()),r.attr("aria-hidden","true"),i.oldTab.attr({"aria-selected":"false","aria-expanded":"false"}),a.length&&r.length?i.oldTab.attr("tabIndex",-1):a.length&&this.tabs.filter(function(){return 0===t(this).attr("tabIndex")}).attr("tabIndex",-1),a.attr("aria-hidden","false"),i.newTab.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0})},_activate:function(e){var i,s=this._findActive(e);s[0]!==this.active[0]&&(s.length||(s=this.active),i=s.find(".ui-tabs-anchor")[0],this._eventHandler({target:i,currentTarget:i,preventDefault:t.noop}))},_findActive:function(e){return e===!1?t():this.tabs.eq(e)},_getIndex:function(e){return"string"==typeof e&&(e=this.anchors.index(this.anchors.filter("[href$='"+t.ui.escapeSelector(e)+"']"))),e},_destroy:function(){this.xhr&&this.xhr.abort(),this.tablist.removeAttr("role").off(this.eventNamespace),this.anchors.removeAttr("role tabIndex").removeUniqueId(),this.tabs.add(this.panels).each(function(){t.data(this,"ui-tabs-destroy")?t(this).remove():t(this).removeAttr("role tabIndex aria-live aria-busy aria-selected aria-labelledby aria-hidden aria-expanded")}),this.tabs.each(function(){var e=t(this),i=e.data("ui-tabs-aria-controls");i?e.attr("aria-controls",i).removeData("ui-tabs-aria-controls"):e.removeAttr("aria-controls")}),this.panels.show(),"content"!==this.options.heightStyle&&this.panels.css("height","")},enable:function(e){var i=this.options.disabled;i!==!1&&(void 0===e?i=!1:(e=this._getIndex(e),i=t.isArray(i)?t.map(i,function(t){return t!==e?t:null}):t.map(this.tabs,function(t,i){return i!==e?i:null})),this._setOptionDisabled(i))},disable:function(e){var i=this.options.disabled;if(i!==!0){if(void 0===e)i=!0;else{if(e=this._getIndex(e),-1!==t.inArray(e,i))return;i=t.isArray(i)?t.merge([e],i).sort():[e]}this._setOptionDisabled(i)}},load:function(e,i){e=this._getIndex(e);var s=this,n=this.tabs.eq(e),o=n.find(".ui-tabs-anchor"),a=this._getPanelForTab(n),r={tab:n,panel:a},h=function(t,e){"abort"===e&&s.panels.stop(!1,!0),s._removeClass(n,"ui-tabs-loading"),a.removeAttr("aria-busy"),t===s.xhr&&delete s.xhr};this._isLocal(o[0])||(this.xhr=t.ajax(this._ajaxSettings(o,i,r)),this.xhr&&"canceled"!==this.xhr.statusText&&(this._addClass(n,"ui-tabs-loading"),a.attr("aria-busy","true"),this.xhr.done(function(t,e,n){setTimeout(function(){a.html(t),s._trigger("load",i,r),h(n,e)},1)}).fail(function(t,e){setTimeout(function(){h(t,e)},1)})))},_ajaxSettings:function(e,i,s){var n=this;return{url:e.attr("href").replace(/#.*$/,""),beforeSend:function(e,o){return n._trigger("beforeLoad",i,t.extend({jqXHR:e,ajaxSettings:o},s))}}},_getPanelForTab:function(e){var i=t(e).attr("aria-controls");return this.element.find(this._sanitizeSelector("#"+i))}}),t.uiBackCompat!==!1&&t.widget("ui.tabs",t.ui.tabs,{_processTabs:function(){this._superApply(arguments),this._addClass(this.tabs,"ui-tab")}}),t.ui.tabs,t.widget("ui.tooltip",{version:"1.12.1",options:{classes:{"ui-tooltip":"ui-corner-all ui-widget-shadow"},content:function(){var e=t(this).attr("title")||"";return t("").text(e).html()},hide:!0,items:"[title]:not([disabled])",position:{my:"left top+15",at:"left bottom",collision:"flipfit flip"},show:!0,track:!1,close:null,open:null},_addDescribedBy:function(e,i){var s=(e.attr("aria-describedby")||"").split(/\s+/);s.push(i),e.data("ui-tooltip-id",i).attr("aria-describedby",t.trim(s.join(" ")))},_removeDescribedBy:function(e){var i=e.data("ui-tooltip-id"),s=(e.attr("aria-describedby")||"").split(/\s+/),n=t.inArray(i,s);-1!==n&&s.splice(n,1),e.removeData("ui-tooltip-id"),s=t.trim(s.join(" ")),s?e.attr("aria-describedby",s):e.removeAttr("aria-describedby")},_create:function(){this._on({mouseover:"open",focusin:"open"}),this.tooltips={},this.parents={},this.liveRegion=t("
    ").attr({role:"log","aria-live":"assertive","aria-relevant":"additions"}).appendTo(this.document[0].body),this._addClass(this.liveRegion,null,"ui-helper-hidden-accessible"),this.disabledTitles=t([])},_setOption:function(e,i){var s=this;this._super(e,i),"content"===e&&t.each(this.tooltips,function(t,e){s._updateContent(e.element)})},_setOptionDisabled:function(t){this[t?"_disable":"_enable"]()},_disable:function(){var e=this;t.each(this.tooltips,function(i,s){var n=t.Event("blur");n.target=n.currentTarget=s.element[0],e.close(n,!0)}),this.disabledTitles=this.disabledTitles.add(this.element.find(this.options.items).addBack().filter(function(){var e=t(this);return e.is("[title]")?e.data("ui-tooltip-title",e.attr("title")).removeAttr("title"):void 0}))},_enable:function(){this.disabledTitles.each(function(){var e=t(this);e.data("ui-tooltip-title")&&e.attr("title",e.data("ui-tooltip-title"))}),this.disabledTitles=t([])},open:function(e){var i=this,s=t(e?e.target:this.element).closest(this.options.items);s.length&&!s.data("ui-tooltip-id")&&(s.attr("title")&&s.data("ui-tooltip-title",s.attr("title")),s.data("ui-tooltip-open",!0),e&&"mouseover"===e.type&&s.parents().each(function(){var e,s=t(this);s.data("ui-tooltip-open")&&(e=t.Event("blur"),e.target=e.currentTarget=this,i.close(e,!0)),s.attr("title")&&(s.uniqueId(),i.parents[this.id]={element:this,title:s.attr("title")},s.attr("title",""))}),this._registerCloseHandlers(e,s),this._updateContent(s,e))},_updateContent:function(t,e){var i,s=this.options.content,n=this,o=e?e.type:null;return"string"==typeof s||s.nodeType||s.jquery?this._open(e,t,s):(i=s.call(t[0],function(i){n._delay(function(){t.data("ui-tooltip-open")&&(e&&(e.type=o),this._open(e,t,i))})}),i&&this._open(e,t,i),void 0)},_open:function(e,i,s){function n(t){l.of=t,a.is(":hidden")||a.position(l)}var o,a,r,h,l=t.extend({},this.options.position);if(s){if(o=this._find(i))return o.tooltip.find(".ui-tooltip-content").html(s),void 0;i.is("[title]")&&(e&&"mouseover"===e.type?i.attr("title",""):i.removeAttr("title")),o=this._tooltip(i),a=o.tooltip,this._addDescribedBy(i,a.attr("id")),a.find(".ui-tooltip-content").html(s),this.liveRegion.children().hide(),h=t("
    ").html(a.find(".ui-tooltip-content").html()),h.removeAttr("name").find("[name]").removeAttr("name"),h.removeAttr("id").find("[id]").removeAttr("id"),h.appendTo(this.liveRegion),this.options.track&&e&&/^mouse/.test(e.type)?(this._on(this.document,{mousemove:n}),n(e)):a.position(t.extend({of:i},this.options.position)),a.hide(),this._show(a,this.options.show),this.options.track&&this.options.show&&this.options.show.delay&&(r=this.delayedShow=setInterval(function(){a.is(":visible")&&(n(l.of),clearInterval(r))},t.fx.interval)),this._trigger("open",e,{tooltip:a})}},_registerCloseHandlers:function(e,i){var s={keyup:function(e){if(e.keyCode===t.ui.keyCode.ESCAPE){var s=t.Event(e);s.currentTarget=i[0],this.close(s,!0)}}};i[0]!==this.element[0]&&(s.remove=function(){this._removeTooltip(this._find(i).tooltip)}),e&&"mouseover"!==e.type||(s.mouseleave="close"),e&&"focusin"!==e.type||(s.focusout="close"),this._on(!0,i,s)},close:function(e){var i,s=this,n=t(e?e.currentTarget:this.element),o=this._find(n);return o?(i=o.tooltip,o.closing||(clearInterval(this.delayedShow),n.data("ui-tooltip-title")&&!n.attr("title")&&n.attr("title",n.data("ui-tooltip-title")),this._removeDescribedBy(n),o.hiding=!0,i.stop(!0),this._hide(i,this.options.hide,function(){s._removeTooltip(t(this))}),n.removeData("ui-tooltip-open"),this._off(n,"mouseleave focusout keyup"),n[0]!==this.element[0]&&this._off(n,"remove"),this._off(this.document,"mousemove"),e&&"mouseleave"===e.type&&t.each(this.parents,function(e,i){t(i.element).attr("title",i.title),delete s.parents[e]}),o.closing=!0,this._trigger("close",e,{tooltip:i}),o.hiding||(o.closing=!1)),void 0):(n.removeData("ui-tooltip-open"),void 0)},_tooltip:function(e){var i=t("
    ").attr("role","tooltip"),s=t("
    ").appendTo(i),n=i.uniqueId().attr("id");return this._addClass(s,"ui-tooltip-content"),this._addClass(i,"ui-tooltip","ui-widget ui-widget-content"),i.appendTo(this._appendTo(e)),this.tooltips[n]={element:e,tooltip:i}},_find:function(t){var e=t.data("ui-tooltip-id");return e?this.tooltips[e]:null},_removeTooltip:function(t){t.remove(),delete this.tooltips[t.attr("id")]},_appendTo:function(t){var e=t.closest(".ui-front, dialog");return e.length||(e=this.document[0].body),e},_destroy:function(){var e=this;t.each(this.tooltips,function(i,s){var n=t.Event("blur"),o=s.element;n.target=n.currentTarget=o[0],e.close(n,!0),t("#"+i).remove(),o.data("ui-tooltip-title")&&(o.attr("title")||o.attr("title",o.data("ui-tooltip-title")),o.removeData("ui-tooltip-title"))}),this.liveRegion.remove()}}),t.uiBackCompat!==!1&&t.widget("ui.tooltip",t.ui.tooltip,{options:{tooltipClass:null},_tooltip:function(){var t=this._superApply(arguments);return this.options.tooltipClass&&t.tooltip.addClass(this.options.tooltipClass),t}}),t.ui.tooltip}); \ No newline at end of file diff --git a/www/js/jquery.js b/www/js/jquery.js new file mode 100644 index 0000000..072e308 --- /dev/null +++ b/www/js/jquery.js @@ -0,0 +1,10220 @@ +/*! + * jQuery JavaScript Library v3.1.1 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2016-09-22T22:30Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var document = window.document; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var concat = arr.concat; + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + + + + function DOMEval( code, doc ) { + doc = doc || document; + + var script = doc.createElement( "script" ); + + script.text = code; + doc.head.appendChild( script ).parentNode.removeChild( script ); + } +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.1.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }, + + // Support: Android <=4.0 only + // Make sure we trim BOM and NBSP + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = jQuery.isArray( copy ) ) ) ) { + + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray( src ) ? src : []; + + } else { + clone = src && jQuery.isPlainObject( src ) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isFunction: function( obj ) { + return jQuery.type( obj ) === "function"; + }, + + isArray: Array.isArray, + + isWindow: function( obj ) { + return obj != null && obj === obj.window; + }, + + isNumeric: function( obj ) { + + // As of jQuery 3.0, isNumeric is limited to + // strings and numbers (primitives or objects) + // that can be coerced to finite numbers (gh-2662) + var type = jQuery.type( obj ); + return ( type === "number" || type === "string" ) && + + // parseFloat NaNs numeric-cast false positives ("") + // ...but misinterprets leading-number strings, particularly hex literals ("0x...") + // subtraction forces infinities to NaN + !isNaN( obj - parseFloat( obj ) ); + }, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + + /* eslint-disable no-unused-vars */ + // See https://github.com/eslint/eslint/issues/6125 + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + type: function( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; + }, + + // Evaluates a script in a global context + globalEval: function( code ) { + DOMEval( code ); + }, + + // Convert dashed to camelCase; used by the css and data modules + // Support: IE <=9 - 11, Edge 12 - 13 + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // Support: Android <=4.0 only + trim: function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var tmp, args, proxy; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; + }, + + now: Date.now, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), +function( i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +} ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = jQuery.type( obj ); + + if ( type === "function" || jQuery.isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.3 + * https://sizzlejs.com/ + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2016-08-08 + */ +(function( window ) { + +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[i] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + + rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), + funescape = function( _, escaped, escapedWhitespace ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + // Support: Firefox<24 + // Workaround erroneous numeric interpretation of +"0x" + return high !== high || escapedWhitespace ? + escaped : + high < 0 ? + // BMP codepoint + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + disabledAncestor = addCombinator( + function( elem ) { + return elem.disabled === true && ("form" in elem || "label" in elem); + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { + + // ID selector + if ( (m = match[1]) ) { + + // Document context + if ( nodeType === 9 ) { + if ( (elem = context.getElementById( m )) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && (elem = newContext.getElementById( m )) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[2] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( (m = match[3]) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !compilerCache[ selector + " " ] && + (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { + + if ( nodeType !== 1 ) { + newContext = context; + newSelector = selector; + + // qSA looks outside Element context, which is not what we want + // Thanks to Andrew Dupont for this workaround technique + // Support: IE <=8 + // Exclude object elements + } else if ( context.nodeName.toLowerCase() !== "object" ) { + + // Capture the context ID, setting it first if necessary + if ( (nid = context.getAttribute( "id" )) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", (nid = expando) ); + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[i] = "#" + nid + " " + toSelector( groups[i] ); + } + newSelector = groups.join( "," ); + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + } + + if ( newSelector ) { + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key + " " ] = value); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement("fieldset"); + + try { + return !!fn( el ); + } catch (e) { + return false; + } finally { + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split("|"), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[i] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + disabledAncestor( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9-11, Edge + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + if ( preferredDoc !== document && + (subWindow = document.defaultView) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert(function( el ) { + el.className = "i"; + return !el.getAttribute("className"); + }); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert(function( el ) { + el.appendChild( document.createComment("") ); + return !el.getElementsByTagName("*").length; + }); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert(function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + }); + + // ID filter and find + if ( support.getById ) { + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode("id"); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( (elem = elems[i++]) ) { + node = elem.getAttributeNode("id"); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find["TAG"] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( (support.qsa = rnative.test( document.querySelectorAll )) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( el ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll("[msallowcapture^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push("~="); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push(".#.+[+~]"); + } + }); + + assert(function( el ) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement("input"); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll("[name=d]").length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll(":enabled").length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll(":disabled").length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( el ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + + // Choose the first element that is related to our preferred document + if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { + return -1; + } + if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + return a === document ? -1 : + b === document ? 1 : + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + if ( support.matchesSelector && documentIsHTML && + !compilerCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch (e) {} + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null; +}; + +Sizzle.escape = function( sel ) { + return (sel + "").replace( rcssescape, fcssescape ); +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + while ( (node = elem[i++]) ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[6] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[3] ) { + match[2] = match[4] || match[5] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { return true; } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + // Use previously-cached element index if available + if ( useCache ) { + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || (node[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + (outerCache[ node.uniqueID ] = {}); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + // Don't keep the element (issue #299) + input[0] = null; + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifier + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( (tokens = []) ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push({ + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + }); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match + }); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {}); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( (oldCache = uniqueCache[ key ]) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return (newCache[ 2 ] = oldCache[ 2 ]); + } else { + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + len = elems.length; + + if ( outermost ) { + outermostContext = context === document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + if ( !context && elem.ownerDocument !== document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context || document, xml) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( (selector = compiled.selector || selector) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) { + + context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert(function( el ) { + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement("fieldset") ) & 1; +}); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert(function( el ) { + el.innerHTML = ""; + return el.firstChild.getAttribute("href") === "#" ; +}) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + }); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert(function( el ) { + el.innerHTML = ""; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; +}) ) { + addHandle( "value", function( elem, name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + }); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert(function( el ) { + return el.getAttribute("disabled") == null; +}) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + (val = elem.getAttributeNode( name )) && val.specified ? + val.value : + null; + } + }); +} + +return Sizzle; + +})( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; +jQuery.escapeSelector = Sizzle.escape; + + + + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +var rneedsContext = jQuery.expr.match.needsContext; + +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +var risSimple = /^.[^:#\[\.,]*$/; + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Simple selector that can be filtered directly, removing non-Elements + if ( risSimple.test( qualifier ) ) { + return jQuery.filter( qualifier, elements, not ); + } + + // Complex selector, compare the two sets, removing non-Elements + qualifier = jQuery.filter( qualifier, elements ); + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not && elem.nodeType === 1; + } ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( jQuery.isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + return elem.contentDocument || jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( jQuery.isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && jQuery.type( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && jQuery.isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && jQuery.isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + resolve.call( undefined, value ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.call( undefined, value ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = jQuery.isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( jQuery.isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + jQuery.isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + jQuery.isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + jQuery.isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the master Deferred + master = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + master.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( master.state() === "pending" || + jQuery.isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return master.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); + } + + return master.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( jQuery.type( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !jQuery.isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; +var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ jQuery.camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ jQuery.camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ jQuery.camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( jQuery.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( jQuery.camelCase ); + } else { + key = jQuery.camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || jQuery.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + jQuery.contains( elem.ownerDocument, elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + +var swap = function( elem, options, callback, args ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.apply( elem, args || [] ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + + + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, + scale = 1, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + do { + + // If previous iteration zeroed out, double until we get *something*. + // Use string for doubling so we don't accidentally see scale as unchanged below + scale = scale || ".5"; + + // Adjust and apply + initialInUnit = initialInUnit / scale; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Update scale, tolerating zero or NaN from tween.cur() + // Break the loop if scale is unchanged or perfect, or if we've just had enough. + } while ( + scale !== ( scale = currentValue() / initial ) && scale !== 1 && --maxIterations + ); + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]+)/i ); + +var rscriptType = ( /^$|\/(?:java|ecma)script/i ); + + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { + + // Support: IE <=9 only + option: [ 1, "" ], + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
    " ], + col: [ 2, "", "
    " ], + tr: [ 2, "", "
    " ], + td: [ 3, "", "
    " ], + + _default: [ 0, "", "" ] +}; + +// Support: IE <=9 only +wrapMap.optgroup = wrapMap.option; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && jQuery.nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, contains, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; +} )(); +var documentElement = document.documentElement; + + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +// Support: IE <=9 only +// See #13393 for more info +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = {}; + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + // Make a writable jQuery.Event from the native event object + var event = jQuery.event.fix( nativeEvent ); + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // Triggered event must either 1) have no namespace, or 2) have namespace(s) + // a subset or equal to those in the bound event (both can have no namespace). + if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: jQuery.isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + focus: { + + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== safeActiveElement() && this.focus ) { + this.focus(); + return false; + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + if ( this === safeActiveElement() && this.blur ) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + click: { + + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) { + this.click(); + return false; + } + }, + + // For cross-browser consistency, don't fire native .click() on links + _default: function( event ) { + return jQuery.nodeName( event.target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + + which: function( event ) { + var button = event.button; + + // Add which for key events + if ( event.which == null && rkeyEvent.test( event.type ) ) { + return event.charCode != null ? event.charCode : event.keyCode; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { + if ( button & 1 ) { + return 1; + } + + if ( button & 2 ) { + return 3; + } + + if ( button & 4 ) { + return 2; + } + + return 0; + } + + return event.which; + } +}, jQuery.event.addProp ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +var + + /* eslint-disable max-len */ + + // See https://github.com/eslint/eslint/issues/3229 + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi, + + /* eslint-enable */ + + // Support: IE <=10 - 11, Edge 12 - 13 + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +function manipulationTarget( elem, content ) { + if ( jQuery.nodeName( elem, "table" ) && + jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return elem.getElementsByTagName( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + + if ( match ) { + elem.type = match[ 1 ]; + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.access( src ); + pdataCur = dataPriv.set( dest, pdataOld ); + events = pdataOld.events; + + if ( events ) { + delete pdataCur.handle; + pdataCur.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = concat.apply( [], args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( isFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl ) { + jQuery._evalUrl( node.src ); + } + } else { + DOMEval( node.textContent.replace( rcleanScript, "" ), doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && jQuery.contains( node.ownerDocument, node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html.replace( rxhtmlTag, "<$1>" ); + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = jQuery.contains( elem.ownerDocument, elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rmargin = ( /^margin/ ); + +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + div.style.cssText = + "box-sizing:border-box;" + + "position:relative;display:block;" + + "margin:auto;border:1px;padding:1px;" + + "top:1%;width:50%"; + div.innerHTML = ""; + documentElement.appendChild( container ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = divStyle.marginLeft === "2px"; + boxSizingReliableVal = divStyle.width === "4px"; + + // Support: Android 4.0 - 4.3 only + // Some styles come back with percentage values, even though they shouldn't + div.style.marginRight = "50%"; + pixelMarginRightVal = divStyle.marginRight === "4px"; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + var pixelPositionVal, boxSizingReliableVal, pixelMarginRightVal, reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + container.style.cssText = "border:0;width:8px;height:0;top:0;left:-9999px;" + + "padding:0;margin-top:1px;position:absolute"; + container.appendChild( div ); + + jQuery.extend( support, { + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelMarginRight: function() { + computeStyleTests(); + return pixelMarginRightVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + style = elem.style; + + computed = computed || getStyles( elem ); + + // Support: IE <=9 only + // getPropertyValue is only needed for .css('filter') (#12537) + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelMarginRight() && rnumnonpx.test( ret ) && rmargin.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }, + + cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style; + +// Return a css property mapped to a potentially vendor prefixed property +function vendorPropName( name ) { + + // Shortcut for names that are not vendor prefixed + if ( name in emptyStyle ) { + return name; + } + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +function setPositiveNumber( elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) { + var i, + val = 0; + + // If we already have the right measurement, avoid augmentation + if ( extra === ( isBorderBox ? "border" : "content" ) ) { + i = 4; + + // Otherwise initialize for horizontal or vertical properties + } else { + i = name === "width" ? 1 : 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin, so add it if we want it + if ( extra === "margin" ) { + val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); + } + + if ( isBorderBox ) { + + // border-box includes padding, so remove it if we want content + if ( extra === "content" ) { + val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // At this point, extra isn't border nor margin, so remove border + if ( extra !== "margin" ) { + val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } else { + + // At this point, extra isn't content, so add padding + val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // At this point, extra isn't content nor padding, so add border + if ( extra !== "padding" ) { + val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + return val; +} + +function getWidthOrHeight( elem, name, extra ) { + + // Start with offset property, which is equivalent to the border-box value + var val, + valueIsBorderBox = true, + styles = getStyles( elem ), + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + if ( elem.getClientRects().length ) { + val = elem.getBoundingClientRect()[ name ]; + } + + // Some non-html elements return undefined for offsetWidth, so check for null/undefined + // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285 + // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668 + if ( val <= 0 || val == null ) { + + // Fall back to computed then uncomputed css if necessary + val = curCSS( elem, name, styles ); + if ( val < 0 || val == null ) { + val = elem.style[ name ]; + } + + // Computed unit is not pixels. Stop here and return. + if ( rnumnonpx.test( val ) ) { + return val; + } + + // Check for style in case a browser which returns unreliable values + // for getComputedStyle silently falls back to the reliable elem.style + valueIsBorderBox = isBorderBox && + ( support.boxSizingReliable() || val === elem.style[ name ] ); + + // Normalize "", auto, and prepare for extra + val = parseFloat( val ) || 0; + } + + // Use the active box-sizing model to add/subtract irrelevant styles + return ( val + + augmentWidthOrHeight( + elem, + name, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: { + "float": "cssFloat" + }, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = jQuery.camelCase( name ), + style = elem.style; + + name = jQuery.cssProps[ origName ] || + ( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName ); + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + if ( type === "number" ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + style[ name ] = value; + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = jQuery.camelCase( name ); + + // Make sure that we're working with the right name + name = jQuery.cssProps[ origName ] || + ( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName ); + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( i, name ) { + jQuery.cssHooks[ name ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, name, extra ); + } ) : + getWidthOrHeight( elem, name, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = extra && getStyles( elem ), + subtract = extra && augmentWidthOrHeight( + elem, + name, + extra, + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + styles + ); + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ name ] = value; + value = jQuery.css( elem, name ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( !rmargin.test( prefix ) ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( jQuery.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && + ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || + jQuery.cssHooks[ tween.prop ] ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +var + fxNow, timerId, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function raf() { + if ( timerId ) { + window.requestAnimationFrame( raf ); + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = jQuery.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 13 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = jQuery.camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( jQuery.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + if ( percent < 1 && length ) { + return remaining; + } else { + deferred.resolveWith( elem, [ animation ] ); + return false; + } + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( jQuery.isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + jQuery.proxy( result.stop, result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( jQuery.isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + // attach callbacks from options + return animation.progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( jQuery.isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + jQuery.isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing + }; + + // Go to the end state if fx are off or if document is hidden + if ( jQuery.fx.off || document.hidden ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( jQuery.isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue && type !== false ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = jQuery.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Checks the timer has not already been removed + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + if ( timer() ) { + jQuery.fx.start(); + } else { + jQuery.timers.pop(); + } +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( !timerId ) { + timerId = window.requestAnimationFrame ? + window.requestAnimationFrame( raf ) : + window.setInterval( jQuery.fx.tick, jQuery.fx.interval ); + } +}; + +jQuery.fx.stop = function() { + if ( window.cancelAnimationFrame ) { + window.cancelAnimationFrame( timerId ); + } else { + window.clearInterval( timerId ); + } + + timerId = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + jQuery.nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://html.spec.whatwg.org/multipage/infrastructure.html#strip-and-collapse-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( jQuery.isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( typeof value === "string" && value ) { + classes = value.match( rnothtmlwhite ) || []; + + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( jQuery.isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + if ( typeof value === "string" && value ) { + classes = value.match( rnothtmlwhite ) || []; + + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) > -1 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value; + + if ( typeof stateVal === "boolean" && type === "string" ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( jQuery.isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + return this.each( function() { + var className, i, self, classNames; + + if ( type === "string" ) { + + // Toggle individual class names + i = 0; + self = jQuery( this ); + classNames = value.match( rnothtmlwhite ) || []; + + while ( ( className = classNames[ i++ ] ) ) { + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, isFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + isFunction = jQuery.isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( jQuery.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( jQuery.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + elem[ type ](); + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup contextmenu" ).split( " " ), + function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + return arguments.length > 0 ? + this.on( name, null, data, fn ) : + this.trigger( name ); + }; +} ); + +jQuery.fn.extend( { + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +} ); + + + + +support.focusin = "onfocusin" in window; + + +// Support: Firefox <=44 +// Firefox doesn't have focus(in | out) events +// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +// +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 +// focus(in | out) events fire after focus & blur events, +// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 +if ( !support.focusin ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + var doc = this.ownerDocument || this, + attaches = dataPriv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this, + attaches = dataPriv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + dataPriv.remove( doc, fix ); + + } else { + dataPriv.access( doc, fix, attaches ); + } + } + }; + } ); +} +var location = window.location; + +var nonce = jQuery.now(); + +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) { + xml = undefined; + } + + if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; +}; + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( jQuery.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && jQuery.type( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = jQuery.isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + // If an array was passed in, assume that it is an array of form elements. + if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ) + .filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ) + .map( function( i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( jQuery.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( jQuery.isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ]; + } + } + match = responseHeaders[ key.toLowerCase() ]; + } + return match == null ? null : match; + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 13 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available, append data to url + if ( s.data ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce++ ) + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( jQuery.isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + + +jQuery._evalUrl = function( url ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + "throws": true + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( jQuery.isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var isFunction = jQuery.isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( isFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain requests + if ( s.crossDomain ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( "