{"version":3,"file":"analysis.js","sources":["analysis.js"],"sourcesContent":["class Analysis {\n constructor(companiesConsidered, cutoff, modelCode, title, listID) {\n this.fields = [];\n this.modelCode = modelCode;\n this.companiesConsidered = companiesConsidered;\n this.cutoff = cutoff;\n this.listSize;\n this.bestEstimateTotalEmployees;\n this.bestEstimateTotalEmployeesCompaniesUsed;\n this.bestEstimateTotalTurnover;\n this.bestEstimateTotalTurnoverCompaniesUsed;\n this.companiesWithDealroomFunding;\n this.totalDealroomFunding;\n this.totalInnovateUkFunding;\n this.title = title;\n this.listID = listID;\n this.bestEstimateGrowthPercentagePerYear;\n this.totalInnovateUKGrantsWon;\n this.bestEstimateGVA;\n this.bestEstimateGVACompaniesUsed;\n this.femaleDirectors;\n this.maleDirectors;\n this.unknownGenderDirectors;\n this.womenLedCompanies;\n this.womenFoundedCompanies;\n this.summaryTable;\n this.companiesWithAnomalies;\n this.filtersObject = null;\n this.filtersForSearch = null;\n this.listFiltersDescription;\n }\n\n getAnalysis(filtersForSearch, callback, callbackArguments) {\n let method, url, input;\n const self = this;\n\n method = \"POST\";\n url = `../api/analyse/analyse_actions.php?action=calculate&modelcode=${this.modelCode}&cutoff=${this.cutoff}${(this.comparisonGroup) ? \"&compare=true\" : \"\"}`;\n input = {\n model_code: this.modelCode,\n cutoff: this.cutoff\n }\n Object.assign(input, filtersForSearch);\n this.filtersForSearch = input;\n\n const getListInsightsSuccess = function (response) {\n const analysisResponse = JSON.parse(response);\n self.processAnalysisResponse(analysisResponse);\n callback(callbackArguments, filtersForSearch);\n }\n\n const getListInsightsError = function () {\n console.log(\"Error getting list insights\");\n }\n\n coreFunctions.xhrHelper(method, url, getListInsightsSuccess, getListInsightsError, JSON.stringify(input));\n }\n\n getSummaryTableData(filtersForSearch, callback, callbackArguments) {\n let method, url, input;\n const self = this;\n\n method = \"POST\";\n url = `../api/analyse/analyse_actions.php?action=summary_table`;\n\n filtersForSearch.filters.CompanyStatus = 'Active';\n input = filtersForSearch;\n\n console.log(input)\n\n const getSummaryTableSuccess = function (response) {\n const summaryTableResponse = JSON.parse(response);\n self.processSummaryTableData(summaryTableResponse);\n }\n\n const getSummaryTableError = function () {\n console.log(\"Error getting list insights\");\n }\n\n coreFunctions.xhrHelper(method, url, getSummaryTableSuccess, getSummaryTableError, JSON.stringify(input));\n }\n\n formatKnowledgeBaseTooltipLink(url, text = \"Find out more\", anchorText = \"here\") {\n return `${text} ${anchorText}.`;\n }\n\n processAnalysisResponse(analysisResponse) {\n let locationQuotientDescription, tooltipText;\n const self = this;\n\n const mapAttributions = \"
Map attributions: Leaflet | © OpenStreetMap contributors © CARTO\";\n const lightcastDataExplainer = \"Job postings data is sourced from Lightcast.\";\n const locationsKnowledgeBaseLink = this.formatKnowledgeBaseTooltipLink(\"https://help.thedatacity.com/knowledge/using-analyse#locations\", \"Find out more about interpreting location results\");\n const keywordsKnowledgeBaseLink = this.formatKnowledgeBaseTooltipLink(\"https://help.thedatacity.com/knowledge/using-analyse#sector-innovation-keywords\");\n const rticsAndSectorsKnowledgeBaseLink = this.formatKnowledgeBaseTooltipLink(\"https://help.thedatacity.com/knowledge/using-analyse#rtics-and-sectors\", \"Find out more about interpreting RTIC and sector results\");\n const companyDetailsKnowledgeBaseLink = this.formatKnowledgeBaseTooltipLink(\"https://help.thedatacity.com/knowledge/using-analyse#company-details\");\n const lightcastLink = ``;\n\n coreFunctions.checkForDebugMessages(analysisResponse);\n //this.resetAnalysis().updateAnalysis(analysisResponse, companyListObject, returnCount);\n this.resetAnalysis().updateAnalysis(analysisResponse, null, null);\n\n // TODO check this works with compare\n //this.updateUrlAddressQuery();\n\n if (this.analysisFieldAvailable(analysisResponse, \"LANameCounts\") && analysisResponse.LANameCounts.length > 0) {\n this.newAnalysisField(\"LANameCounts\", \"Business counts by local authority\", \"Locations\", \"numerical\", `${locationsKnowledgeBaseLink}${mapAttributions}`, \"bar\", [\"bar\"], \"Local authority\", null, false, \"LANames\", \"Local authority\").addAnalysisFieldElements(analysisResponse.LANameCounts, \"Thing\").addTotalCount(analysisResponse.CompaniesConsidered);\n }\n if (this.analysisFieldAvailable(analysisResponse, \"EmployeesByLA\") && analysisResponse.EmployeesByLA.length > 0) {\n this.newAnalysisField(\"EmployeesByLA\", \"Employees by local authority\", \"Locations\", \"numerical\", `${locationsKnowledgeBaseLink}${mapAttributions}`, \"bar\", [\"bar\"], \"Local authority\", null, false, \"LANames\", \"Local authority\").addAnalysisFieldElements(analysisResponse.EmployeesByLA, \"Thing\").addTotalCount(analysisResponse.BestEstimateTotalEmployees);\n }\n if (this.analysisFieldAvailable(analysisResponse, \"TurnoversByLA\") && analysisResponse.TurnoversByLA.length > 0) {\n this.newAnalysisField(\"TurnoversByLA\", \"Turnovers by local authority\", \"Locations\", \"currency\", `${locationsKnowledgeBaseLink}${mapAttributions}`, \"bar\", [\"bar\"], \"Local authority\", null, false, \"LANames\", \"Local authority\").addAnalysisFieldElements(analysisResponse.TurnoversByLA, \"Thing\").addTotalCount(analysisResponse.BestEstimateTotalTurnover);\n }\n if (this.analysisFieldAvailable(analysisResponse, \"CityCounts\") && analysisResponse.CityCounts.length > 0) {\n this.newAnalysisField(\"CityCounts\", \"Business counts by city\", \"Locations\", \"numerical\", `${locationsKnowledgeBaseLink}${mapAttributions}`, \"bar\", [\"bar\"], \"Standardised city\", \"Not in a major city\", false, \"Cities\", \"OECD functional urban area\").addAnalysisFieldElements(analysisResponse.CityCounts, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"EmployeesByCity\") && analysisResponse.EmployeesByCity.length > 0) {\n this.newAnalysisField(\"EmployeesByCity\", \"Employees by city\", \"Locations\", \"numerical\", `${locationsKnowledgeBaseLink}${mapAttributions}`, \"bar\", [\"bar\"], \"Standardised city\", \"Not in a major city\", false, \"Cities\", \"OECD functional urban area\").addAnalysisFieldElements(analysisResponse.EmployeesByCity, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"TurnoversByCity\") && analysisResponse.TurnoversByCity.length > 0) {\n this.newAnalysisField(\"TurnoversByCity\", \"Turnovers by city\", \"Locations\", \"currency\", `${locationsKnowledgeBaseLink}${mapAttributions}`, \"bar\", [\"bar\"], \"Standardised city\", \"Not in a major city\", false, \"Cities\", \"OECD functional urban area\").addAnalysisFieldElements(analysisResponse.TurnoversByCity, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"ConstituencyCounts\") && analysisResponse.ConstituencyCounts.length > 0) {\n this.newAnalysisField(\"ConstituencyCounts\", \"Business counts by constituency\", \"Locations\", \"numerical\", `${locationsKnowledgeBaseLink}`, \"bar\", [\"bar\"], \"Constituency\", null, false, \"Constituencies\", \"Constituencies\").addAnalysisFieldElements(analysisResponse.ConstituencyCounts, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"EmployeesByConstituency\") && analysisResponse.EmployeesByConstituency.length > 0) {\n this.newAnalysisField(\"EmployeesByConstituency\", \"Employees by constituency\", \"Locations\", \"numerical\", `${locationsKnowledgeBaseLink}`, \"bar\", [\"bar\"], \"Constituency\", null, false, \"Constituencies\", \"Constituencies\").addAnalysisFieldElements(analysisResponse.EmployeesByConstituency, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"TurnoversByConstituency\") && analysisResponse.TurnoversByConstituency.length > 0) {\n this.newAnalysisField(\"TurnoversByConstituency\", \"Turnovers by constituency\", \"Locations\", \"currency\", `${locationsKnowledgeBaseLink}`, \"bar\", [\"bar\"], \"Constituency\", null, false, \"Constituencies\", \"Constituencies\").addAnalysisFieldElements(analysisResponse.TurnoversByConstituency, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"LEPCounts\") && analysisResponse.LEPCounts.length > 0) {\n this.newAnalysisField(\"LEPCounts\", \"Business counts by LEP\", \"Locations\", \"numerical\", `${locationsKnowledgeBaseLink}`, \"bar\", [\"bar\"], \"LEP\", \"Not in a LEP\", false, \"LEPNames\", \"Local enterprise partnerships\").addAnalysisFieldElements(analysisResponse.LEPCounts, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"EmployeesByLEP\") && analysisResponse.EmployeesByLEP.length > 0) {\n this.newAnalysisField(\"EmployeesByLEP\", \"Employees by LEP\", \"Locations\", \"numerical\", `${locationsKnowledgeBaseLink}${mapAttributions}`, \"bar\", [\"bar\"], \"LEP\", \"Not in a LEP\", false, \"LEPNames\", \"Local enterprise partnerships\").addAnalysisFieldElements(analysisResponse.EmployeesByLEP, \"Thing\").addTotalCount(analysisResponse.BestEstimateTotalEmployees);\n }\n if (this.analysisFieldAvailable(analysisResponse, \"TurnoversByLEP\") && analysisResponse.TurnoversByLEP.length > 0) {\n this.newAnalysisField(\"TurnoversByLEP\", \"Turnovers by LEP\", \"Locations\", \"currency\", `${locationsKnowledgeBaseLink}${mapAttributions}`, \"bar\", [\"bar\"], \"LEP\", \"Not in a LEP\", false, \"LEPNames\", \"Local enterprise partnerships\").addAnalysisFieldElements(analysisResponse.TurnoversByLEP, \"Thing\").addTotalCount(analysisResponse.BestEstimateTotalTurnover);\n }\n if (this.analysisFieldAvailable(analysisResponse, \"NUTS1Counts\") && analysisResponse.NUTS1Counts.length > 0) {\n this.newAnalysisField(\"NUTS1Counts\", \"Business counts by ITL1 region\", \"Locations\", \"numerical\", `${locationsKnowledgeBaseLink}`, \"bar\", [\"bar\"], \"ITL1 region\", null, false, \"ITL1\", \"ITL1 region\").addAnalysisFieldElements(analysisResponse.NUTS1Counts, \"Name\").addTotalCount(analysisResponse.CompaniesConsidered);\n }\n if (this.analysisFieldAvailable(analysisResponse, \"EmployeesByITL1\") && analysisResponse.EmployeesByITL1.length > 0) {\n this.newAnalysisField(\"EmployeesByITL1\", \"Employees by ITL1 region\", \"Locations\", \"numerical\", `${locationsKnowledgeBaseLink}`, \"bar\", [\"bar\"], \"ITL1 region\", null, false, \"ITL1\", \"ITL1 region\").addAnalysisFieldElements(analysisResponse.EmployeesByITL1, \"Thing\").addTotalCount(analysisResponse.BestEstimateTotalEmployees);\n }\n if (this.analysisFieldAvailable(analysisResponse, \"TurnoversByITL1\") && analysisResponse.TurnoversByITL1.length > 0) {\n this.newAnalysisField(\"TurnoversByITL1\", \"Turnovers by ITL1 region\", \"Locations\", \"currency\", `${locationsKnowledgeBaseLink}`, \"bar\", [\"bar\"], \"ITL1 region\", null, false, \"ITL1\", \"ITL1 region\").addAnalysisFieldElements(analysisResponse.TurnoversByITL1, \"Thing\").addTotalCount(analysisResponse.BestEstimateTotalTurnover);\n }\n if (this.analysisFieldAvailable(analysisResponse, \"NUTS2CodeCounts\") && analysisResponse.NUTS2CodeCounts.length > 0) {\n this.newAnalysisField(\"NUTS2CodeCounts\", \"Business counts by ITL2 region\", \"Locations\", \"numerical\", `${locationsKnowledgeBaseLink}`, \"bar\", [\"bar\"], \"ITL2 region\", null, false, \"ITL2\", \"ITL2 region\").addAnalysisFieldElements(analysisResponse.NUTS2CodeCounts, \"ID\").addTotalCount(analysisResponse.CompaniesConsidered);\n }\n if (this.analysisFieldAvailable(analysisResponse, \"EmployeesByITL2\") && analysisResponse.EmployeesByITL2.length > 0) {\n this.newAnalysisField(\"EmployeesByITL2\", \"Employees by ITL2 region\", \"Locations\", \"numerical\", `${locationsKnowledgeBaseLink}`, \"bar\", [\"bar\"], \"ITL2 region\", null, false, \"ITL2\", \"ITL2 region\").addAnalysisFieldElements(analysisResponse.EmployeesByITL2, \"ID\").addTotalCount(analysisResponse.BestEstimateTotalEmployees);\n }\n if (this.analysisFieldAvailable(analysisResponse, \"TurnoversByITL2\") && analysisResponse.TurnoversByITL2.length > 0) {\n this.newAnalysisField(\"TurnoversByITL2\", \"Turnovers by ITL2 region\", \"Locations\", \"currency\", `${locationsKnowledgeBaseLink}`, \"bar\", [\"bar\"], \"ITL2 region\", null, false, \"ITL2\", \"ITL2 region\").addAnalysisFieldElements(analysisResponse.TurnoversByITL2, \"ID\").addTotalCount(analysisResponse.BestEstimateTotalTurnover);\n }\n if (this.analysisFieldAvailable(analysisResponse, \"UltimateParentNationsCounts\") && analysisResponse.UltimateParentNationsCounts.length > 0) {\n this.newAnalysisField(\"UltimateParentNationsCounts\", `Business counts by ultimate parent nation`, \"Locations\", \"numerical\", null, \"bar\", [\"bar\"], \"Ultimate parent nation\", null, false, \"UltimateParentCompanyNation\", \"Ultimate parent company nation\").addAnalysisFieldElements(analysisResponse.UltimateParentNationsCounts, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"UltimateParentNationsEmployees\") && analysisResponse.UltimateParentNationsEmployees.length > 0) {\n this.newAnalysisField(\"UltimateParentNationsEmployees\", `Employees by ultimate parent nation`, \"Locations\", \"numerical\", null, \"bar\", [\"bar\"], \"Ultimate parent nation\", null, false, \"UltimateParentCompanyNation\", \"Ultimate parent company nation\").addAnalysisFieldElements(analysisResponse.UltimateParentNationsEmployees, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"UltimateParentNationsTurnovers\") && analysisResponse.UltimateParentNationsTurnovers.length > 0) {\n this.newAnalysisField(\"UltimateParentNationsTurnovers\", `Turnovers by ultimate parent nation`, \"Locations\", \"currency\", null, \"bar\", [\"bar\"], \"Ultimate parent nation\", null, false, \"UltimateParentCompanyNation\", \"Ultimate parent company nation\").addAnalysisFieldElements(analysisResponse.UltimateParentNationsTurnovers, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"SectorKeywordCounts\") && analysisResponse.SectorKeywordCounts.length > 0) {\n this.newAnalysisField(\"SectorKeywordCounts\", \"Sector keyword counts\", \"Website keywords\", \"numerical\", `We maintain a list of keywords that are indicative of a company operating in a specific sector. This chart shows the number of companies whose websites contain those words.${keywordsKnowledgeBaseLink}`, \"bar\", [\"bar\"], \"Sector keyword\", null, true, null, \"Sector keywords\").addAnalysisFieldElements(analysisResponse.SectorKeywordCounts, \"Thing\").addTotalCount(analysisResponse.CompaniesConsidered);\n }\n if (this.analysisFieldAvailable(analysisResponse, \"SectorKeywordEnrichment\") && analysisResponse.SectorKeywordEnrichment.length > 0) {\n this.newAnalysisField(\"SectorKeywordEnrichment\", \"Sector keywords enrichment\", \"Website keywords\", \"percentage\", `The sector keywords that are over-represented and under-represented among your companies compared to the average UK company. For example, a value of \\\"Social Media +95%\\\" means that the companies in your list and matching your filters are 95% more likely to mention the phrase \\\"social media\\\" on their website than the average UK company.${keywordsKnowledgeBaseLink}`, \"bar\", [\"bar\"], \"Sector keyword\", null, false, null, \"Sector keywords\").addAnalysisFieldElements(analysisResponse.SectorKeywordEnrichment, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"InnovationKeywordCounts\") && analysisResponse.InnovationKeywordCounts.length > 0) {\n this.newAnalysisField(\"InnovationKeywordCounts\", \"Innovation keyword counts\", \"Website keywords\", \"numerical\", `We maintain a list of keywords that suggest that a company may be innovative. This chart shows the number of companies whose websites contain those words.${keywordsKnowledgeBaseLink}`, \"bar\", [\"bar\"], \"Innovation keyword\", null, true, null, \"Innovation keywords\").addAnalysisFieldElements(analysisResponse.InnovationKeywordCounts, \"Thing\").addTotalCount(analysisResponse.CompaniesConsidered);\n }\n if (this.analysisFieldAvailable(analysisResponse, \"InnovationKeywordEnrichment\") && analysisResponse.InnovationKeywordEnrichment.length > 0) {\n this.newAnalysisField(\"InnovationKeywordEnrichment\", \"Innovation keywords enrichment\", \"Website keywords\", \"percentage\", `The sector keywords that are over-represented and under-represented among your companies compared to the average UK company. For example, a value of \\\"Export +200%\\\" means that the companies in your list and matching your filters are 200% more likely (3x) to mention the phrase \\\"export\\\" on their website than the average UK company.${keywordsKnowledgeBaseLink}`, \"bar\", [\"bar\"], \"Innovation keyword\", null, false, null, \"Innovation keywords\").addAnalysisFieldElements(analysisResponse.InnovationKeywordEnrichment, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"FounderGenderBusinessCounts\") && analysisResponse.FounderGenderBusinessCounts.length > 0) {\n const founderGenderTooltip = \"For more information on how we define female founders, as well as how we recommend using this data, please read this article\";\n // Configure stacked barchart fields can be shared between others\n let stackedBarchartOptions = {\n sharedFieldId: \"mixed\",\n fieldIdsSharingMixedTotal: [\"women\", \"men\"]\n }\n this.newAnalysisField(\"FounderGenderBusinessCounts\", \"Business counts by founder gender\", \"Company details\", \"numerical\", founderGenderTooltip, \"bar_stacked\", [\"bar_stacked\"], \"Gender\", null, false, null, \"People\", stackedBarchartOptions).addAnalysisFieldElements(analysisResponse.FounderGenderBusinessCounts, \"Name\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"CompanySizeEmployees\") && analysisResponse.CompanySizeEmployees.length > 0) {\n this.newAnalysisField(\"CompanySizeEmployees\", \"Company size by employees\", \"Company details\", \"numerical\", null, \"bar\", [\"bar\"], \"Company size\", null, false, null, \"People\").addAnalysisFieldElements(analysisResponse.CompanySizeEmployees, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"EmployeesByYear\")) {\n this.newAnalysisField(\"EmployeesByYear\", \"Employees by year\", \"Company details\", \"numerical\", null, \"line\", [\"bar\", \"line\"], null, null, false, null, \"People\").addAnalysisFinancialFieldElements(analysisResponse.EmployeesByYear.MeasuredYearValues, analysisResponse.EmployeesByYear.ProjectedYearValues).createTopCompanies(analysisResponse.EmployeesByYear.TopCompanies ?? null, true);\n }\n if (this.analysisFieldAvailable(analysisResponse, \"CompaniesActiveOrStruckoff\") && analysisResponse.CompaniesActiveOrStruckoff.length > 0) {\n this.newAnalysisField(\"CompaniesActiveOrStruckoff\", \"Company active or struck off\", \"Company details\", \"numerical\", null, \"bar\", [\"bar\"], \"Company status\", null, false, null, \"Company growth\").addAnalysisFieldElements(analysisResponse.CompaniesActiveOrStruckoff, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"CompanyStatuses\") && analysisResponse.CompanyStatuses.length > 0) {\n this.newAnalysisField(\"CompanyStatuses\", \"Company status\", \"Company details\", \"numerical\", null, \"bar\", [\"bar\"], \"Company status\", null, false, null, \"Company growth\").addAnalysisFieldElements(analysisResponse.CompanyStatuses, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"CompanyGrowthStages\") && analysisResponse.CompanyGrowthStages.length > 0) {\n const companySizeTooltip = \"Our company sizes are based on the UK's legislative definition on company sizes. You can read more about the categorisation here.\";\n this.newAnalysisField(\"CompanyGrowthStages\", \"Company size\", \"Company details\", \"numerical\", companySizeTooltip, \"bar\", [\"bar\"], \"Growth stage\", null, false, null, \"Company growth\").addAnalysisFieldElements(analysisResponse.CompanyGrowthStages, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"CompanyGrowthRates\") && analysisResponse.CompanyGrowthRates.length > 0) {\n this.newAnalysisField(\"CompanyGrowthRates\", \"Company growth rate\", \"Company details\", \"numerical\", null, \"bar\", [\"bar\"], \"Growth rate\", null, false, null, \"Company growth\").addAnalysisFieldElements(analysisResponse.CompanyGrowthRates, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"JobPostingInsights\") && analysisResponse.JobPostingInsights.length > 0) {\n const charts = [{\n type: \"table\",\n title: \"Jobs table\",\n subfield: \"jobinsights\",\n headings: [\"SOC4 code\", \"SOC4 name\", \"Postings\", \"Median salary\"],\n title: \"Job postings (SOC4)\",\n columns: [\n new DataTableColumn(\"soc4Code\", \"text\"),\n new DataTableColumn (\"soc4Name\", \"text\"),\n new DataTableColumn(\"postings\", \"number\"),\n new DataTableColumn(\"medianSalary\", \"currency\")\n ],\n drilldown: {\n keyField: \"soc4Code\",\n labelField: \"soc4Name\",\n chart: \"line\"\n }\n }, {\n type: \"line\",\n subtype: \"timestamp\",\n title: \"Job postings over time\",\n subfield: \"jobinsights\",\n defaults: {\n field: \"soc4Code\",\n label: \"All\",\n value: \"All\"\n }\n }\n ];\n this.newAnalysisField(\"JobPostingInsights\", \"Job insights\", \"Jobs and skills\", \"numerical\", lightcastDataExplainer, charts, [\"line\"], \"Year\", null, false, null, \"Job posting insights\").addJobPostingInsightsElements(analysisResponse.JobPostingInsights);\n }\n if (this.analysisFieldAvailable(analysisResponse, \"CompanyJobPostingsOverTimeBySOC4\") && analysisResponse.CompanyJobPostingsOverTimeBySOC4.length > 0) {\n this.newAnalysisField(\"CompanyJobPostingsOverTimeBySOC4\", \"Job postings over time (SOC4)\", \"Jobs and skills\", \"numerical\", lightcastDataExplainer, \"line\", [\"line\"], \"Timestamp\", null, false, null, \"SOC4\").addAnalysisFieldElements(this.processJobPostingsOverTimeData(analysisResponse.CompanyJobPostingsOverTimeBySOC4), \"Thing\");\n }\n\n if (this.analysisFieldAvailable(analysisResponse, \"CompanyJobPostingsOverTimeByLOT\") && analysisResponse.CompanyJobPostingsOverTimeByLOT.length > 0) {\n this.newAnalysisField(\"CompanyJobPostingsOverTimeByLOT\", \"Job postings over time (LOT)\", \"Jobs and skills\", \"numerical\", lightcastDataExplainer, \"line\", [\"line\"], \"Timestamp\", null, false, null, \"LOT\").addAnalysisFieldElements(this.processJobPostingsOverTimeData(analysisResponse.CompanyJobPostingsOverTimeByLOT), \"Thing\");\n }\n\n if (this.analysisFieldAvailable(analysisResponse, \"JobPostingsBySOC4Name\") && analysisResponse.JobPostingsBySOC4Name.length > 0) {\n this.newAnalysisField(\"JobPostingsBySOC4Name\", `Job postings by SOC4 name`, \"Jobs and skills\", \"numerical\", lightcastDataExplainer, \"bar\", [\"bar\"], \"SOC4 Name\", null, false, null, \"SOC4\").addAnalysisFieldElements(analysisResponse.JobPostingsBySOC4Name, \"Thing\");\n }\n // if (this.analysisFieldAvailable(analysisResponse, \"JobPostingsBySOC4Code\") && analysisResponse.JobPostingsBySOC4Code.length > 0) {\n // this.newAnalysisField(\"JobPostingsBySOC4Code\", `Job postings by SOC4 code`, \"Jobs and skills\", \"numerical\", lightcastDataExplainer, \"bar\", [\"bar\"], \"SOC4 Code\", null, false, null, \"SOC4 code\").addAnalysisFieldElements(analysisResponse.JobPostingsBySOC4Code, \"Thing\");\n // }\n if (this.analysisFieldAvailable(analysisResponse, \"AverageAdvertisedSalaryBySOC4Name\") && analysisResponse.AverageAdvertisedSalaryBySOC4Name.length > 0) {\n this.newAnalysisField(\"AverageAdvertisedSalaryBySOC4Name\", `Average advertised salary by SOC4 name`, \"Jobs and skills\", \"currency\", lightcastDataExplainer, \"bar\", [\"bar\"], \"SOC4 Name\", null, false, null, \"SOC4\").addAnalysisFieldElements(analysisResponse.AverageAdvertisedSalaryBySOC4Name, \"Thing\");\n }\n // if (this.analysisFieldAvailable(analysisResponse, \"AverageAdvertisedSalaryBySOC4Code\") && analysisResponse.AverageAdvertisedSalaryBySOC4Code.length > 0) {\n // this.newAnalysisField(\"AverageAdvertisedSalaryBySOC4Code\", `Average advertised salary by SOC4 code`, \"Jobs and skills\", \"currency\", lightcastDataExplainer, \"bar\", [\"bar\"], \"SOC4 Code\", null, false, null, \"SOC4 code\").addAnalysisFieldElements(analysisResponse.AverageAdvertisedSalaryBySOC4Code, \"Thing\");\n // }\n if (this.analysisFieldAvailable(analysisResponse, \"JobPostingsBySkill\") && analysisResponse.JobPostingsBySkill.length > 0) {\n this.newAnalysisField(\"JobPostingsBySkill\", `Jobs postings by skill`, \"Jobs and skills\", \"numerical\", lightcastDataExplainer, \"bar\", [\"bar\"], \"Skill\", null, false, null, \"Skills\").addAnalysisFieldElements(analysisResponse.JobPostingsBySkill.slice(0, 520), \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"AveragePostingDurationBySkill\") && analysisResponse.AveragePostingDurationBySkill.length > 0) {\n this.newAnalysisField(\"AveragePostingDurationBySkill\", `Average posting duration by skill`, \"Jobs and skills\", \"numerical\", lightcastDataExplainer, \"bar\", [\"bar\"], \"Skill\", null, false, null, \"Skills\").addAnalysisFieldElements(analysisResponse.AveragePostingDurationBySkill.slice(0, 520), \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse,'JobPostingsBySoftwareSkill')) {\n this.newAnalysisField(\"JobPostingsBySoftwareSkill\", `Job postings by software skill`, \"Jobs and skills\", \"numerical\", lightcastDataExplainer, \"bar\", [\"bar\"], \"Skill\", null, false, null, \"Skills\").addAnalysisFieldElements(analysisResponse.JobPostingsBySoftwareSkill, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"AveragePostingDurationBySoftwareSkill\") && analysisResponse.AveragePostingDurationBySoftwareSkill.length > 0) {\n this.newAnalysisField(\"AveragePostingDurationBySoftwareSkill\", `Average posting duration by software skill`, \"Jobs and skills\", \"numerical\", lightcastDataExplainer, \"bar\", [\"bar\"], \"Skill\", null, false, null, \"Skills\").addAnalysisFieldElements(analysisResponse.AveragePostingDurationBySoftwareSkill, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse,'JobPostingsBySpecializedSkill')) {\n this.newAnalysisField(\"JobPostingsBySpecializedSkill\", `Job postings by specialised skill`, \"Jobs and skills\", \"numerical\", lightcastDataExplainer, \"bar\", [\"bar\"], \"Skill\", null, false, null, \"Skills\").addAnalysisFieldElements(analysisResponse.JobPostingsBySpecializedSkill, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"AveragePostingDurationBySpecializedSkill\") && analysisResponse.AveragePostingDurationBySpecializedSkill.length > 0) {\n this.newAnalysisField(\"AveragePostingDurationBySpecializedSkill\", `Average posting duration by specialised skill`, \"Jobs and skills\", \"numerical\", lightcastDataExplainer, \"bar\", [\"bar\"], \"Skill\", null, false, null, \"Skills\").addAnalysisFieldElements(analysisResponse.AveragePostingDurationBySpecializedSkill, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse,'JobPostingsByCertificationsSkill')) {\n this.newAnalysisField(\"JobPostingsByCertificationsSkill\", `Job postings by certification skill`, \"Jobs and skills\", \"numerical\", lightcastDataExplainer, \"bar\", [\"bar\"], \"Skill\", null, false, null, \"Skills\").addAnalysisFieldElements(analysisResponse.JobPostingsByCertificationsSkill, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"AveragePostingDurationByCertificationsSkill\") && analysisResponse.AveragePostingDurationByCertificationsSkill.length > 0) {\n this.newAnalysisField(\"AveragePostingDurationByCertificationsSkill\", `Average posting duration by certification skill`, \"Jobs and skills\", \"numerical\", lightcastDataExplainer, \"bar\", [\"bar\"], \"Skill\", null, false, null, \"Skills\").addAnalysisFieldElements(analysisResponse.AveragePostingDurationByCertificationsSkill, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse,'JobPostingsByCommonSkill')) {\n this.newAnalysisField(\"JobPostingsByCommonSkill\", `Job postings by common skill`, \"Jobs and skills\", \"numerical\", lightcastDataExplainer, \"bar\", [\"bar\"], \"Skill\", null, false, null, \"Skills\").addAnalysisFieldElements(analysisResponse.JobPostingsByCommonSkill, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"AveragePostingDurationByCommonSkill\") && analysisResponse.AveragePostingDurationByCommonSkill.length > 0) {\n this.newAnalysisField(\"AveragePostingDurationByCommonSkill\", `Average posting duration by common skill`, \"Jobs and skills\", \"numerical\", lightcastDataExplainer, \"bar\", [\"bar\"], \"Skill\", null, false, null, \"Skills\").addAnalysisFieldElements(analysisResponse.AveragePostingDurationByCommonSkill, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"RTICBusinessCounts\") && analysisResponse.RTICBusinessCounts.length > 0) {\n this.newAnalysisField(\"RTICBusinessCounts\", \"RTIC sector counts\", \"RTICs and sectors\", \"numerical\", `${rticsAndSectorsKnowledgeBaseLink}`, \"bar\", [\"bar\"], \"RTIC\", null, false, null, \"RTIC sectors\").addAnalysisFieldElements(analysisResponse.RTICBusinessCounts, \"Thing\").addTotalCount(analysisResponse.CompaniesConsidered);\n }\n if (this.analysisFieldAvailable(analysisResponse, \"RTICEmployees\") && analysisResponse.RTICEmployees.length > 0) {\n this.newAnalysisField(\"RTICEmployees\", \"Employees by RTIC sector\", \"RTICs and sectors\", \"numerical\", `${rticsAndSectorsKnowledgeBaseLink}`, \"bar\", [\"bar\"], \"RTIC\", null, false, null, \"RTIC sectors\").addAnalysisFieldElements(analysisResponse.RTICEmployees, \"Thing\").addTotalCount(analysisResponse.BestEstimateTotalEmployees);\n }\n if (this.analysisFieldAvailable(analysisResponse, \"RTICJobPostings\") && analysisResponse.RTICJobPostings.length > 0) {\n this.newAnalysisField(\"RTICJobPostings\", `Job postings by RTIC sector ${lightcastLink}`, \"RTICs and sectors\", \"numerical\", lightcastDataExplainer, \"bar\", [\"bar\"], \"RTIC\", null, false, null, \"RTIC sectors\").addAnalysisFieldElements(analysisResponse.RTICJobPostings, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"RTICTurnovers\") && analysisResponse.RTICTurnovers.length > 0) {\n this.newAnalysisField(\"RTICTurnovers\", \"Turnovers by RTIC sector\", \"RTICs and sectors\", \"currency\", `${rticsAndSectorsKnowledgeBaseLink}`, \"bar\", [\"bar\"], \"RTIC\", null, false, null, \"RTIC sectors\").addAnalysisFieldElements(analysisResponse.RTICTurnovers, \"Thing\").addTotalCount(analysisResponse.BestEstimateTotalTurnover);\n }\n if (this.analysisFieldAvailable(analysisResponse, \"RTICVerticalBusinessCounts\") && analysisResponse.RTICVerticalBusinessCounts.length > 0) {\n this.newAnalysisField(\"RTICVerticalBusinessCounts\", \"RTIC vertical business counts\", \"RTICs and sectors\", \"numerical\", `${rticsAndSectorsKnowledgeBaseLink}`, \"bar\", [\"bar\"], \"RTIC vertical\", null, false, null, \"RTIC verticals\").addAnalysisFieldElements(analysisResponse.RTICVerticalBusinessCounts, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"RTICVerticalEmployees\") && analysisResponse.RTICVerticalEmployees.length > 0) {\n this.newAnalysisField(\"RTICVerticalEmployees\", \"Employees by RTIC vertical\", \"RTICs and sectors\", \"numerical\", `${rticsAndSectorsKnowledgeBaseLink}`, \"bar\", [\"bar\"], \"RTIC vertical\", null, false, null, \"RTIC verticals\").addAnalysisFieldElements(analysisResponse.RTICVerticalEmployees, \"Thing\").addTotalCount(analysisResponse.BestEstimateTotalEmployees);\n }\n if (this.analysisFieldAvailable(analysisResponse, \"RTICVerticalJobPostings\") && analysisResponse.RTICVerticalJobPostings.length > 0) {\n this.newAnalysisField(\"RTICVerticalJobPostings\", `Job postings by RTIC vertical ${lightcastLink}`, \"RTICs and sectors\", \"numerical\", lightcastDataExplainer, \"bar\", [\"bar\"], \"RTIC\", null, false, null, \"RTIC verticals\").addAnalysisFieldElements(analysisResponse.RTICVerticalJobPostings, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"RTICVerticalTurnovers\") && analysisResponse.RTICVerticalTurnovers.length > 0) {\n this.newAnalysisField(\"RTICVerticalTurnovers\", \"Turnovers by RTIC vertical\", \"RTICs and sectors\", \"currency\", `${rticsAndSectorsKnowledgeBaseLink}`, \"bar\", [\"bar\"], \"RTIC vertical\", null, false, null, \"RTIC verticals\").addAnalysisFieldElements(analysisResponse.RTICVerticalTurnovers, \"Thing\").addTotalCount(analysisResponse.BestEstimateTotalTurnover);\n }\n if (this.analysisFieldAvailable(analysisResponse, \"CICBusinessCounts\") && analysisResponse.CICBusinessCounts.length > 0) {\n this.newAnalysisField(\"CICBusinessCounts\", \"Business counts by CIC sector\", \"RTICs and sectors\", \"numerical\", null, \"bar\", [\"bar\"], \"CIC sector\", null, false, null, \"CIC sectors\").addAnalysisFieldElements(analysisResponse.CICBusinessCounts, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"CICEmployees\") && analysisResponse.CICEmployees.length > 0) {\n this.newAnalysisField(\"CICEmployees\", \"Employees by CIC sector\", \"RTICs and sectors\", \"numerical\", null, \"bar\", [\"bar\"], \"CIC sector\", null, false, null, \"CIC sectors\").addAnalysisFieldElements(analysisResponse.CICEmployees, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"CICJobPostings\") && analysisResponse.CICJobPostings.length > 0) {\n this.newAnalysisField(\"CICJobPostings\", `Job postings by CIC sector ${lightcastLink}`, \"RTICs and sectors\", \"numerical\", lightcastDataExplainer, \"bar\", [\"bar\"], \"CIC sector\", null, false, null, \"CIC sectors\").addAnalysisFieldElements(analysisResponse.CICJobPostings, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"CICTurnovers\") && analysisResponse.CICTurnovers.length > 0) {\n this.newAnalysisField(\"CICTurnovers\", \"Turnovers by CIC sector\", \"RTICs and sectors\", \"currency\", null, \"bar\", [\"bar\"], \"CIC sector\", null, false, null, \"CIC sectors\").addAnalysisFieldElements(analysisResponse.CICTurnovers, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"CICVerticalBusinessCounts\") && analysisResponse.CICVerticalBusinessCounts.length > 0) {\n this.newAnalysisField(\"CICVerticalBusinessCounts\", \"Business counts by CIC vertical\", \"RTICs and sectors\", \"numerical\", null, \"bar\", [\"bar\"], \"CIC vertical\", null, false, null, \"CIC verticals\").addAnalysisFieldElements(analysisResponse.CICVerticalBusinessCounts, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"CICVerticalEmployees\") && analysisResponse.CICVerticalEmployees.length > 0) {\n this.newAnalysisField(\"CICVerticalEmployees\", \"Employees by CIC vertical\", \"RTICs and sectors\", \"numerical\", null, \"bar\", [\"bar\"], \"CIC vertical\", null, false, null, \"CIC verticals\").addAnalysisFieldElements(analysisResponse.CICVerticalEmployees, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"CICVerticalJobPostings\") && analysisResponse.CICVerticalJobPostings.length > 0) {\n this.newAnalysisField(\"CICVerticalJobPostings\", `Job postings by CIC vertical ${lightcastLink}`, \"RTICs and sectors\", \"numerical\", lightcastDataExplainer, \"bar\", [\"bar\"], \"CIC vertical\", null, false, null, \"CIC verticals\").addAnalysisFieldElements(analysisResponse.CICVerticalJobPostings, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"CICVerticalTurnovers\") && analysisResponse.CICVerticalTurnovers.length > 0) {\n this.newAnalysisField(\"CICVerticalTurnovers\", \"Turnovers by CIC vertical\", \"RTICs and sectors\", \"currency\", null, \"bar\", [\"bar\"], \"CIC vertical\", null, false, null, \"CIC verticals\").addAnalysisFieldElements(analysisResponse.CICVerticalTurnovers, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"SICCounts\") && analysisResponse.SICCounts.length > 0) {\n this.newAnalysisField(\"SICCounts\", \"Business counts by SIC code\", \"RTICs and sectors\", \"numerical\", `${rticsAndSectorsKnowledgeBaseLink}`, \"bar\", [\"bar\"], \"SIC\", null, false, null, \"SIC codes\").addAnalysisFieldElements(analysisResponse.SICCounts, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"SICEmployees\") && analysisResponse.SICEmployees.length > 0) {\n this.newAnalysisField(\"SICEmployees\", \"Employees by SIC code\", \"RTICs and sectors\", \"numerical\", `${rticsAndSectorsKnowledgeBaseLink}`, \"bar\", [\"bar\"], \"SIC\", null, false, null, \"SIC codes\").addAnalysisFieldElements(analysisResponse.SICEmployees, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"SICTurnovers\") && analysisResponse.SICTurnovers.length > 0) {\n this.newAnalysisField(\"SICTurnovers\", \"Turnovers by SIC code\", \"RTICs and sectors\", \"currency\", `${rticsAndSectorsKnowledgeBaseLink}`, \"bar\", [\"bar\"], \"SIC\", null, false, null, \"SIC codes\").addAnalysisFieldElements(analysisResponse.SICTurnovers, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"RSICCounts\") && analysisResponse.RSICCounts.length > 0) {\n this.newAnalysisField(\"RSICCounts\", \"Business counts by RSIC code\", \"RTICs and sectors\", \"numerical\", `${rticsAndSectorsKnowledgeBaseLink}`, \"bar\", [\"bar\"], \"RSIC\", null, false, null, \"RSIC codes\").addAnalysisFieldElements(analysisResponse.RSICCounts, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"RSICEmployees\") && analysisResponse.RSICEmployees.length > 0) {\n this.newAnalysisField(\"RSICEmployees\", \"Employees by RSIC code\", \"RTICs and sectors\", \"numerical\", `${rticsAndSectorsKnowledgeBaseLink}`, \"bar\", [\"bar\"], \"RSIC\", null, false, null, \"RSIC codes\").addAnalysisFieldElements(analysisResponse.RSICEmployees, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"RSICTurnovers\") && analysisResponse.RSICTurnovers.length > 0) {\n this.newAnalysisField(\"RSICTurnovers\", \"Turnovers by RSIC code\", \"RTICs and sectors\", \"currency\", `${rticsAndSectorsKnowledgeBaseLink}`, \"bar\", [\"bar\"], \"RSIC\", null, false, null, \"RSIC codes\").addAnalysisFieldElements(analysisResponse.RSICTurnovers, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"SICHLUCounts\") && analysisResponse.SICHLUCounts.length > 0) {\n this.newAnalysisField(\"SICHLUCounts\", \"Business counts by SIC section\", \"RTICs and sectors\", \"numerical\", `${rticsAndSectorsKnowledgeBaseLink}`, \"bar\", [\"bar\"], \"SIC\", null, false, null, \"SIC sections\").addAnalysisFieldElements(analysisResponse.SICHLUCounts, \"Thing\").addTotalCount(analysisResponse.CompaniesConsidered);\n }\n if (this.analysisFieldAvailable(analysisResponse, \"SICHLUEmployees\") && analysisResponse.SICHLUEmployees.length > 0) {\n this.newAnalysisField(\"SICHLUEmployees\", \"Employees by SIC section\", \"RTICs and sectors\", \"numerical\", `${rticsAndSectorsKnowledgeBaseLink}`, \"bar\", [\"bar\"], \"SIC\", null, false, null, \"SIC sections\").addAnalysisFieldElements(analysisResponse.SICHLUEmployees, \"Thing\").addTotalCount(analysisResponse.BestEstimateTotalEmployees);\n }\n if (this.analysisFieldAvailable(analysisResponse, \"SICHLUTurnovers\") && analysisResponse.SICHLUTurnovers.length > 0) {\n this.newAnalysisField(\"SICHLUTurnovers\", \"Turnovers by SIC section\", \"RTICs and sectors\", \"currency\", `${rticsAndSectorsKnowledgeBaseLink}`, \"bar\", [\"bar\"], \"SIC\", null, false, null, \"SIC sections\").addAnalysisFieldElements(analysisResponse.SICHLUTurnovers, \"Thing\").addTotalCount(analysisResponse.BestEstimateTotalTurnover);\n }\n // if (this.analysisFieldAvailable(analysisResponse, \"CompanyFoundingDates\") && analysisResponse.CompanyFoundingDates.length > 0) {\n // tooltipText = `This graph represents the cumulative growth of companies within the selected filters. It uses the incorporation date of active companies. Companies that have dissolved are not visualised.
For example, if a specific RTIC was selected (e.g. AI), for a previous year it does not represent how many AI businesses were classified in that year. It is showing data only for companies which are now part of the AI RTIC.${companyDetailsKnowledgeBaseLink}`\n // this.newAnalysisField(\"CompanyFoundingDates\", \"Company founding dates\", \"Company details\", \"numerical\", tooltipText, [\"bar\", \"line\"], [\"bar\", \"line\"], \"Year\", null, false, null, \"Founding dates\").addAnalysisFieldElements(analysisResponse.CompanyFoundingDates.reverse(), \"Year\");\n // }\n if (this.analysisFieldAvailable(analysisResponse, \"CompanyActiveDates\") && analysisResponse.CompanyActiveDates.length > 0) {\n tooltipText = 'For more information on how we define company births and deaths, as well as how we recommend using this data, please read this article';\n const charts = [\n {\n type: \"bar\",\n title: \"Company founding years\",\n xField: \"year\",\n yField: \"foundedCount\"\n }, {\n type: \"line\",\n subtype: \"series\",\n title: \"Cumulative active companies by year\",\n subfield: \"activeCompanies\",\n series: [{\n xField: \"year\",\n yField: \"activeCount\",\n cumulateValues: false,\n color: \"#D10956\",\n label: \"Total active companies\"\n }]\n }, {\n type: \"line\",\n subtype: \"series\",\n title: \"Company births and deaths by year\",\n subfield: \"companyBirthsAndDeaths\",\n series: [{\n xField: \"year\",\n yField: \"foundedCount\",\n cumulateValues: false,\n color: \"#00FF00\",\n label: \"Company births\"\n }, {\n xField: \"year\",\n yField: \"diedCount\",\n cumulateValues: false,\n color: \"#FF6600\",\n label: \"Company deaths\"\n }]\n }, {\n type: \"slider\",\n title: \"Company births and deaths summary\",\n min: 2000,\n max: new Date().getFullYear(),\n starts: [2015, new Date().getFullYear()],\n sliderField: \"year\",\n series: [{\n xField: \"year\",\n yField: \"foundedCount\",\n color: \"#00FF00\",\n label: \"Company births\"\n }, {\n xField: \"year\",\n yField: \"diedCount\",\n color: \"#FF6600\",\n label: \"Company deaths\"\n }],\n compareField: {\n yField: \"activeCount\",\n label: \"Active Companies\",\n xField: \"year\"\n }\n }\n ];\n const analysisField = this.newAnalysisField(\"CompanyActiveDates\", \"Company births and deaths\", \"Company details\", \"numerical\", tooltipText, charts, [\"line\"], \"Year\", null, false, null, \"Company births and deaths\").addCompanyActiveDatesElements(analysisResponse.CompanyActiveDates);\n }\n\n if (this.analysisFieldAvailable(analysisResponse, \"TradeImportByChapter\") && analysisResponse.TradeImportByChapter.length > 0) {\n this.newAnalysisField(\"CompanyTradeDataImport\", \"Number of importing firms by HS chapter\", \"Company details\", \"numerical\", \"\", \"bar\", [\"bar\"], \"Chapter\", null, false, null, \"Company trade data\").addAnalysisFieldElements(analysisResponse.TradeImportByChapter, \"Thing\").addTotalCount(analysisResponse.TradeImportTotalCompanies);\n }\n\n if (this.analysisFieldAvailable(analysisResponse, \"TradeExportByChapter\") && analysisResponse.TradeExportByChapter.length > 0) {\n this.newAnalysisField(\"CompanyTradeDataExport\", \"Number of exporting firms by HS chapter\", \"Company details\", \"numerical\", \"\", \"bar\", [\"bar\"], \"Chapter\", null, false, null, \"Company trade data\").addAnalysisFieldElements(analysisResponse.TradeExportByChapter, \"Thing\").addTotalCount(analysisResponse.TradeExportTotalCompanies);\n }\n\n if (this.analysisFieldAvailable(analysisResponse, \"NET_WORTH\")) {\n this.newAnalysisField(\"NET_WORTH\", \"Net worth\", \"Financials\", \"currency\", null, \"line\", [\"bar\", \"line\"], null, null, false, null, \"Financial details\").addAnalysisFinancialFieldElements(analysisResponse.NET_WORTH.MeasuredYearValues, analysisResponse.NET_WORTH.ProjectedYearValues).createTopCompanies(analysisResponse.NET_WORTH.TopCompanies, true);\n }\n if (this.analysisFieldAvailable(analysisResponse, \"TurnoverByYear\")) {\n this.newAnalysisField(\"TurnoverByYear\", \"Turnover by year\", \"Financials\", \"currency\", null, \"line\", [\"bar\", \"line\"], null, null, false, null, \"Financial details\").addAnalysisFinancialFieldElements(analysisResponse.TurnoverByYear.MeasuredYearValues, analysisResponse.TurnoverByYear.ProjectedYearValues).createTopCompanies(analysisResponse.TurnoverByYear.TopCompanies ?? null, true);\n }\n if (this.analysisFieldAvailable(analysisResponse, \"PROFIT_AFTER_TAX\")) {\n this.newAnalysisField(\"PROFIT_AFTER_TAX\", \"Profit after tax\", \"Financials\", \"currency\", null, \"line\", [\"bar\", \"line\"], null, null, false, null, \"Financial details\").addAnalysisFinancialFieldElements(analysisResponse.PROFIT_AFTER_TAX.MeasuredYearValues, analysisResponse.PROFIT_AFTER_TAX.ProjectedYearValues).createTopCompanies(analysisResponse.PROFIT_AFTER_TAX.TopCompanies, true);\n }\n if (this.analysisFieldAvailable(analysisResponse, \"TOTAL_CURRENT_ASSETS\")) {\n this.newAnalysisField(\"TOTAL_CURRENT_ASSETS\", \"Total current assets\", \"Financials\", \"currency\", null, \"line\", [\"bar\", \"line\"], null, null, false, null, \"Financial details\").addAnalysisFinancialFieldElements(analysisResponse.TOTAL_CURRENT_ASSETS.MeasuredYearValues, analysisResponse.TOTAL_CURRENT_ASSETS.ProjectedYearValues).createTopCompanies(analysisResponse.TOTAL_CURRENT_ASSETS.TopCompanies, true);\n }\n if (this.analysisFieldAvailable(analysisResponse, \"TOTAL_LIABILITIES\")) {\n this.newAnalysisField(\"TOTAL_LIABILITIES\", \"Total liabilities\", \"Financials\", \"currency\", null, \"line\", [\"bar\", \"line\"], null, null, false, null, \"Financial details\").addAnalysisFinancialFieldElements(analysisResponse.TOTAL_LIABILITIES.MeasuredYearValues, analysisResponse.TOTAL_LIABILITIES.ProjectedYearValues).createTopCompanies(analysisResponse.TOTAL_LIABILITIES.TopCompanies, true);\n }\n if (this.analysisFieldAvailable(analysisResponse, \"DealroomFundingByYear\") && analysisResponse.DealroomFundingByYear.length > 0) {\n this.newAnalysisField(\"DealroomFundingByYear\", `Total investment funding by year`, \"Financials\", \"currency\", null, \"bar\", [\"bar\"], \"Year\", null, false, null, \"Funding\").addAnalysisFieldElements(this.processDealroomData(analysisResponse.DealroomFundingByYear), \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"DealroomFundingRoundsByYear\")) {\n this.newAnalysisField(\"DealroomFundingRoundsByYear\", `Total investment funding rounds by year
`, \"Financials\", \"number\", null, \"bar\", [\"bar\"], \"Year\", null, false, null, \"Funding\").addAnalysisFieldElements(analysisResponse.DealroomFundingRoundsByYear, \"Thing\");\n }\n if (this.analysisFieldAvailable(analysisResponse, \"InnovateUKFundingByYear\") && analysisResponse.InnovateUKFundingByYear.length > 0) {\n this.newAnalysisField(\"InnovateUKFundingByYear\", \"Innovate UK grant funding by year\", \"Financials\", \"currency\", null, \"bar\", [\"bar\"], \"Year\", null, false, null, \"Funding\").addAnalysisFieldElements(this.processInnovateUkData(analysisResponse.InnovateUKFundingByYear), \"Thing\");\n }\n\n }\n\n setSummaryTableToggleEvent() {\n const self = this;\n document.getElementById('summaryTableToggle').addEventListener('change', (element) => {\n if(element.target.checked == true && document.querySelectorAll('.listSummaryWrapper .summary-table-wrapper tbody tr').length == 0) {\n document.getElementById(\"insightsLoadingListName\").innerHTML = \"Summary Stats\";\n document.getElementById(\"listInsightsPageLoadingHolder\").classList.add(\"active\");\n window.scrollTo({top: 0, behavior: 'smooth'}); // Scroll to top of page\n\n self.getSummaryTableData(this.filtersForSearch);\n }\n });\n }\n\n analysisLoaded(filtersObject) {\n this.filtersObject = filtersObject;\n this.updateUrlAddressQuery().resetAnalysisContent().updateAnalysisFiltersDescription().createWidgetSubsections().createAnalysisWidgets().syncSubsubsectionbubbleMapWidgets().createAnalysisSummaryBox().addAnalyisSummary().displaySectionNavigation();\n this.setSummaryTableToggleEvent();\n }\n\n updateAnalysisFiltersDescription() {\n if (document.getElementById(\"listFiltersDescription\")) {\n const start = 'Analysis of companies';\n const humanReadableFiltersRepresentation = this.filtersObject.getHumanReadableFiltersRepresentation(start);\n document.getElementById(\"listFiltersDescription\").innerHTML = humanReadableFiltersRepresentation;\n const regex = new RegExp('^' + start);\n this.listFiltersDescription = humanReadableFiltersRepresentation.replace(regex, \"\").trim();\n }\n return this;\n }\n\n analysisFieldAvailable(data, field) {\n if (data.hasOwnProperty(field) && data[field] != undefined) {\n if (Array.isArray(data[field]) && data[field].length == 0) {\n return true;\n } else {\n return true;\n }\n } else {\n return false;\n }\n }\n\n checkUrlQueryFields() {\n let queryListID, queryModelCode, queryCutoff, queryListSize, exploreListID, queryRtic, rticSector, rticVertical;\n if (queryListID = coreFunctions.getQueryVariable(\"list_id\")) {\n companyLists.presetList(queryListID);\n }\n if ((queryListID = coreFunctions.getQueryVariable(\"list_id\")) && (queryModelCode = coreFunctions.getQueryVariable(\"model_code\"))) {\n companyLists.validatePresetModelCode(Number(queryListID), queryModelCode);\n }\n if (queryCutoff = coreFunctions.getQueryVariable(\"list_cutoff\")) {\n document.getElementById(\"listCutoffInput\").value = queryCutoff;\n }\n if ((queryListSize = coreFunctions.getQueryVariable(\"list_size\")) && document.querySelector(`#cutoffByListSizeSelect option[value='${queryListSize}']`)) {\n document.getElementById(\"cutoffByListSizeSelect\").value = queryListSize;\n companyLists.toggleCutoffControl(document.getElementById(\"cutoffByListSizeControlHolder\"));\n }\n if (queryRtic = coreFunctions.getQueryVariable(\"rtic\")) {\n rticSector = decodeURIComponent(queryRtic);\n rticVertical = (coreFunctions.getQueryVariable(\"vertical\") ? decodeURIComponent(coreFunctions.getQueryVariable(\"vertical\")) : null);\n filters.rticFilters.preloadRticSector(rticSector, rticVertical);\n }\n }\n\n newAnalysisField(field, label, subsection, dataType, explanation, chartType, allowedChartTypes, thingColumnLabel, emptyFieldLabel, displayDataAsPercentage, locationFilterField, subSubsection, stackedBarchartOptions) {\n let analysisField = new AnalysisField(field, label, subsection, dataType, explanation, chartType, allowedChartTypes, this.listID, thingColumnLabel, emptyFieldLabel, displayDataAsPercentage, locationFilterField, this.title, this, subSubsection, stackedBarchartOptions);\n this.fields.push(analysisField);\n return analysisField;\n }\n\n setAnalysisProperties(modelCode, title, listID, cutoff) {\n this.modelCode = modelCode;\n this.title = title;\n this.listID = listID;\n this.cutoff = cutoff ?? 0;\n return this;\n }\n\n resetAnalysis() {\n this.fields = [];\n this.companiesConsidered = null;\n return this;\n }\n\n updateAnalysis(analysis, listDetails, returnCount) {\n this.fields = [];\n this.companiesConsidered = analysis.CompaniesConsidered;\n this.cutoff = analysis.Cutoff;\n this.bestEstimateTotalEmployees = analysis.BestEstimateTotalEmployees;\n this.bestEstimateTotalEmployeesCompaniesUsed = analysis.BestEstimateTotalEmployeesCompaniesUsed;\n this.bestEstimateTotalTurnover = analysis.BestEstimateTotalTurnover;\n this.bestEstimateTotalTurnoverCompaniesUsed = analysis.BestEstimateTotalTurnoverCompaniesUsed;\n this.totalDealroomFunding = analysis.TotalDealroomFunding * 1000000;\n this.companiesWithDealroomFunding = analysis.CompaniesWithDealroomFunding;\n this.totalInnovateUkFunding = analysis.TotalInnovateUKFunding;\n this.totalInnovateUKGrantsWon = analysis.TotalInnovateUKGrantsWon;\n this.bestEstimateGrowthPercentagePerYear = analysis.BestEstimateGrowthPercentagePerYear;\n this.bestEstimateGrowthPercentagePerYearCompaniesUsed = analysis.BestEstimateGrowthPercentagePerYearCompaniesUsed;\n this.bestEstimateGVA = analysis.BestEstimateGVA;\n this.bestEstimateGVACompaniesUsed = analysis.BestEstimateGVACompaniesUsed;\n this.femaleDirectors = analysis.ActiveFemaleDirectors;\n this.maleDirectors = analysis.ActiveMaleDirectors;\n this.unknownGenderDirectors = analysis.ActiveUnknownGenderDirectors;\n this.womenLedCompanies = analysis.WomenLedCompanies;\n this.womenFoundedCompanies = analysis.WomenFoundedCompanies;\n this.summaryTable = analysis.SummaryTable ?? null;\n this.companiesWithAnomalies = analysis.CompaniesWithAnomalousFinancials ?? null;\n this.tradeImportTotalCompanies = analysis.TradeImportTotalCompanies ?? null;\n this.tradeExportTotalCompanies = analysis.TradeExportTotalCompanies ?? null;\n return this;\n }\n\n createWidgetSubsections() {\n const self = this;\n let listSummaryHolder = document.getElementById(\"listSummaryHolder\");\n const subsections = [\"Summary\"].concat(this.fields.map(element => element.subsection).filter((item, i, ar) => ar.indexOf(item) === i));\n subsections.forEach((subsection) => {\n let subsectionHolder = coreFunctions.createElementHelper(\"div\", { class: \"subsection\", \"data-section\": subsection });\n listSummaryHolder.appendChild(subsectionHolder);\n if (subsection != \"Summary\") {\n subsectionHolder.appendChild(coreFunctions.createElementHelper(\"h2\", { innerHTML: `${subsection}${(subsection == \"Jobs and skills\") ? `
` : \"\"}` })); // Title of subsection, add Lightcast logo for Jobs and skills section\n }\n self.createSubSubsectionSelect(subsection, subsectionHolder)\n\n subsectionHolder.appendChild(coreFunctions.createElementHelper(\"div\", { class: \"mainDataHolder\" })); // Holder for widgets\n\n document.getElementById(\"sectionsList\").appendChild(coreFunctions.createElementHelper(\"button\", {innerHTML: subsection, class: \"sectionElement\", \"data-section\": subsection}, {click: function() {\n document.querySelector(`.subsection[data-section='${this.dataset.section}']`).scrollIntoView({behavior: \"smooth\", block: \"start\"});\n }}));\n })\n return this;\n }\n\n createSubSubsectionSelect(subsection, subsectionHolder) {\n const self = this;\n let subSubsectionControls, subSubsectionSelect;\n\n // Find widgets matching current subsection that also belong to a subsubsection, remove duplicates\n const subSubsections = new Set(analyse.listAnalysis.fields.flatMap((element) => {\n const match = (element.subsection == subsection && element.subSubsection);\n return (match) ? [element.subSubsection] : [];\n }));\n\n // Create subsubsection control elements\n subSubsections.forEach((subSubsection) => {\n if (subSubsection) {\n if (!subSubsectionControls) {\n subSubsectionControls = subsectionHolder.appendChild(coreFunctions.createElementHelper(\"div\", {class: \"subSubsectionControls\", \"data-subsection\": subsection}));\n let subSubsectionSelectHolder = subSubsectionControls.appendChild(coreFunctions.createElementHelper(\"div\", {class: \"subSubSectionControl subSubsectionSelectHolder\", innerHTML: \"Field:\"}));\n subSubsectionSelect = subSubsectionSelectHolder.appendChild(coreFunctions.createElementHelper(\"select\", {class: \"subSubsectionSelect\", \"data-subsection\": subsection}, {change: () => self.subSubsectionOnchange(event)}));\n }\n subSubsectionSelect.appendChild(coreFunctions.createElementHelper(\"option\", {value: subSubsection, innerHTML: subSubsection}));\n }\n });\n }\n\n subSubsectionOnchange(event) {\n const subsection = event.target.dataset.subsection;\n const subSubsection = event.target.value;\n this.hideSubSubsectionWidgets(subsection).processActiveSubSubsection(subsection, subSubsection).refreshSubSubsectionWidgetExplanation(subsection, subSubsection);\n return this;\n }\n\n processActiveSubSubsection(subsection, subSubsection) {\n const subSubsectionWidgets = document.querySelectorAll(`.subsection[data-section='${subsection}'] .locationDataHolder[data-subsubsection='${subSubsection}'], .subsection[data-section='${subsection}'] .listSummaryPanel[data-subsubsection='${subSubsection}']`);\n // Subsubsections have already been created, show the elements\n if (subSubsectionWidgets.length > 0) {\n this.showSubSubsectionWidgets(subSubsectionWidgets)\n const subSubsectionFields = this.fields.filter(element => (element.subsection == subsection && element.subSubsection == subSubsection));\n this.checkExtraSubSubSectionControls(subSubsectionFields, subsection, subSubsection);\n } else {\n this.createSubSubsectionWidgets(subsection, subSubsection)\n }\n return this;\n }\n\n createSubSubsectionWidgets(subsection, subSubsection) {\n const subSubsectionFields = this.fields.filter(element => (element.subsection == subsection && element.subSubsection == subSubsection));\n subSubsectionFields.forEach(element => element.createAnalysisWidget());\n this.checkExtraSubSubSectionControls(subSubsectionFields, subsection, subSubsection);\n return this;\n }\n\n showSubSubsectionWidgets(widgets) {\n widgets.forEach(element => {\n element.classList.remove(\"hidden\");\n // TODO I could maybe add another class to do the thing of\n });\n return this;\n }\n\n checkExtraSubSubSectionControls(widgets, subsection, subSubsection) {\n let existingControl;\n // Check fields for location quotient data\n if (widgets.find(widget => widget.fieldHasLocationQuotientData())) {\n this.processLocationQuotientSubSubSectionControl(subsection, subSubsection);\n } else if (existingControl = document.getElementById(`${subsection}-lq-datasource-control`)) {\n existingControl.classList.add(\"hidden\");\n }\n // Check fields for percentage format data\n if (widgets.find(widget => widget.hasPercentageData())) {\n this.processAbsolutePercentageSubSubSectionControl(subsection, subSubsection);\n } else if (existingControl = document.getElementById(`${subsection}-pc-datasource-control`)) {\n existingControl.classList.add(\"hidden\");\n }\n this.checkSubSubsectionChartTypes(widgets, subsection, subSubsection);\n return this;\n }\n\n processLocationQuotientSubSubSectionControl(subsection, subSubsection) {\n let existingControl = document.getElementById(`${subsection}-lq-datasource-control`);\n if (existingControl) {\n this.updateLocationQuotientSubSubSectionControl(subsection, subSubsection);\n } else if (!this.filterByLocationIsActive()) {\n this.createLocationQuotientSubSubSectionControl(subsection, subSubsection);\n }\n }\n\n filterByLocationIsActive() {\n return this.filtersObject.generalFilters.filterGroupIsActive(\"locations\");\n }\n\n updateLocationQuotientSubSubSectionControl(subsection, subSubsection) {\n const self = this;\n const holder = document.getElementById(`${subsection}-lq-datasource-control`);\n holder.dataset.subSubsection = subSubsection;\n Array.from(holder.querySelectorAll(\"input\")).forEach(control => {\n control.dataset.subsubsection = subSubsection;\n if (control.checked) {\n self.locationQuotientSubSubSectionChange(control);\n }\n })\n holder.classList.remove(\"hidden\");\n }\n\n createLocationQuotientSubSubSectionControl(subsection, subSubsection) {\n const locationQuotientTooltip = \"
Find out more here.
\";\n\n if (!document.getElementById(`${subsection}-lq-datasource-control`)) {\n const controls = coreFunctions.createElementHelper(\"form\", {id: `${subsection}-lq-datasource-control`, \"data-subsection\": subsection, \"data-subsubsection\": subSubsection, class: \"chartSourceDataControls subSubSectionControl\", \"data-source\": \"location-quotient\" }, { change: (event) => this.locationQuotientSubSubSectionChange(event.target)});\n\n controls.appendChild(coreFunctions.createElementHelper(\"span\", {class: 'subSubsectionControlLabel', innerHTML:\"Data format:\"}));\n\n controls.appendChild(coreFunctions.createElementHelper(\"input\", { type: \"radio\", id: `${subsection}-absolute`, name: `${subSubsection}-chartSourceData`, value: \"absolute\", checked: true, \"data-subsection\": subsection, \"data-subsubsection\": subSubsection }));\n\n controls.appendChild(coreFunctions.createElementHelper(\"label\", { for: `${subsection}-absolute`, innerHTML: \"Absolute\" }));\n\n controls.appendChild(coreFunctions.createElementHelper(\"input\", { type: \"radio\", id: `${subsection}-locationquotient`, name: `${subSubsection}-chartSourceData`, value: \"locationquotient\", \"data-subsection\": subsection, \"data-subsubsection\": subSubsection }));\n\n let locationQuotientLabel = controls.appendChild(coreFunctions.createElementHelper(\"label\", { for: `${subsection}-locationquotient`, innerHTML: `Location quotient` }));\n\n locationQuotientLabel.appendChild(coreFunctions.createTooltipElement(locationQuotientTooltip, `${subsection}-lq-tooltip`));\n\n document.querySelector(`.subsection[data-section='${subsection}'] .subSubsectionControls`).appendChild(controls);\n }\n\n return this;\n }\n\n locationQuotientSubSubSectionChange(element) {\n const dataSource = element.value;\n const subsection = element.dataset.subsection;\n const subSubsection = element.dataset.subsubsection;\n const widgets = this.fields.filter(element => element.subsection == subsection && element.subSubsection == subSubsection);\n widgets.forEach(widget => widget.locationQuotientControlChange(dataSource));\n this.syncSubsubsectionbubbleMapWidgets();\n }\n\n processAbsolutePercentageSubSubSectionControl(subsection, subSubsection) {\n let existingControl = document.getElementById(`${subsection}-pc-datasource-control`);\n if (existingControl) {\n this.updateAbsolutePercentageSubSubSectionControl(subsection, subSubsection);\n } else {\n this.createAbsolutePercentageSubSubSectionControl(subsection, subSubsection);\n }\n\n }\n\n createAbsolutePercentageSubSubSectionControl(subsection, subSubsection) {\n const controls = coreFunctions.createElementHelper(\"form\", {id: `${subsection}-pc-datasource-control`, \"data-subsection\": subsection, \"data-subsubsection\": subSubsection, class: \"chartSourceDataControls subSubSectionControl\", \"data-source\": \"location-quotient\" }, { change: (event) => this.absolutePercentageSubSubSectionChange(event.target)});\n\n controls.appendChild(coreFunctions.createElementHelper(\"span\", {class: 'subSubsectionControlLabel', innerHTML:\"Data format:\"}));\n\n controls.appendChild(coreFunctions.createElementHelper(\"input\", { type: \"radio\", id: `${subsection}-absolute-data`, name: `${subSubsection}-chartSourceData`, value: \"absolute\", checked: true, \"data-subsection\": subsection, \"data-subsubsection\": subSubsection }));\n\n controls.appendChild(coreFunctions.createElementHelper(\"label\", { for: `${subsection}-absolute-data`, innerHTML: \"Absolute\" }));\n\n controls.appendChild(coreFunctions.createElementHelper(\"input\", { type: \"radio\", id: `${subsection}-percentage-data`, name: `${subSubsection}-chartSourceData`, value: \"percentage\", \"data-subsection\": subsection, \"data-subsubsection\": subSubsection }));\n\n controls.appendChild(coreFunctions.createElementHelper(\"label\", { for: `${subsection}-percentage-data`, innerHTML: `Percentage` }));\n\n document.querySelector(`.subsection[data-section='${subsection}'] .subSubsectionControls`).appendChild(controls);\n }\n\n updateAbsolutePercentageSubSubSectionControl(subsection, subSubsection) {\n const self = this;\n const holder = document.getElementById(`${subsection}-pc-datasource-control`);\n holder.dataset.subSubsection = subSubsection;\n Array.from(holder.querySelectorAll(\"input\")).forEach(control => {\n control.dataset.subsubsection = subSubsection;\n if (control.checked) {\n self.absolutePercentageSubSubSectionChange(control);\n }\n })\n holder.classList.remove(\"hidden\");\n }\n\n absolutePercentageSubSubSectionChange(element) {\n const dataSource = element.value;\n const subsection = element.dataset.subsection;\n const subSubsection = element.dataset.subsubsection;\n const widgets = this.fields.filter(element => element.subsection == subsection && element.subSubsection == subSubsection);\n widgets.forEach(widget => widget.absolutePercentageDataToggleChange(dataSource));\n }\n\n // Get all widgets currently active in subsection and hide them\n hideSubSubsectionWidgets(subsection) {\n const widgets = document.querySelectorAll(`.subsection[data-section='${subsection}'] .locationDataHolder:not(.hidden), .subsection[data-section='${subsection}'] .listSummaryPanel[data-subsubsection]:not(.hidden)`);\n widgets.forEach(element => {\n element.classList.add(\"hidden\");\n });\n return this;\n }\n\n resetAnalysisContent() {\n let listSummaryHolder, sectionsList;\n listSummaryHolder = document.getElementById(\"listSummaryHolder\");\n while (listSummaryHolder.children[0].nextElementSibling) {\n listSummaryHolder.removeChild(listSummaryHolder.children[0].nextElementSibling); // Remove page content except sections holder\n }\n sectionsList = document.getElementById(\"sectionsList\");\n while (sectionsList.firstChild) {\n sectionsList.removeChild(sectionsList.firstChild);\n }\n return this;\n }\n\n createAnalysisWidgets() {\n this.fields.forEach(field => {\n if (field.addAnalyseFieldToPage()) {\n field.createAnalysisWidget();\n }\n });\n return this;\n }\n\n // All widgets with maps, group by subsubsector and sync map movements and zooms\n syncSubsubsectionbubbleMapWidgets() {\n const bubbleMapWidgets = this.fields.filter(element => element.bubbleMap);\n for (var i = 0; i < bubbleMapWidgets.length; i++) {\n let widget = bubbleMapWidgets[i];\n let siblingbubbleMapWidgets = bubbleMapWidgets.flatMap(element => {return (element.subSubsection == widget.subSubsection && element.field != widget.field) ? [element.bubbleMap] : [] } );\n // Sync widget map with all maps in subsubsector\n siblingbubbleMapWidgets.forEach(siblingBubbleMap => widget.bubbleMap.map.sync(siblingBubbleMap.map));\n }\n return this;\n }\n\n checkSubSubsectionChartTypes(subSubsectionFields, subsection, subSubsection) {\n // Remove widgets with combination of charts, in array format\n const allowedChartTypes = Array.from(new Set(subSubsectionFields.flatMap(element => (!Array.isArray(element.chartType)) ? [...element.allowedChartTypes.map(element => element.split(\"_\")[0])] : [])));\n this.removeSubSubsectionChartTypesControl(subsection);\n if (allowedChartTypes.length > 1) {\n this.createSubSubsectionChartTypesControl(subsection, subSubsection);\n }\n return this;\n }\n\n createSubSubsectionChartTypesControl(subsection, subSubsection) {\n if (!document.getElementById(`${subsection}-chart-type-control`)) {\n const subSubsectionFields = this.fields.filter(element => element.subSubsection == subSubsection);\n // Use split to remove subtypes of charts\n const allowedChartTypes = new Set(subSubsectionFields.flatMap(element => [...element.allowedChartTypes.map(element => element.split(\"_\")[0])]));\n // Ignore subtypes of chart, e.g. bar_stack\n const modalChartType = coreFunctions.getMode(subSubsectionFields.flatMap((element) => {return (!element.chartType.includes(\"_\")) ? [element.chartType] : [];}));\n const controls = coreFunctions.createElementHelper(\"form\", {id: `${subsection}-chart-type-control`, \"data-subsection\": subsection, \"data-subsubsection\": subSubsection, class: \"chartSourceDataControls subSubSectionControl\" }, { change: (event) => this.chartTypeSubSubsectionChange(event.target)});\n\n controls.appendChild(coreFunctions.createElementHelper(\"span\", {class: 'subSubsectionControlLabel', innerHTML:\"Chart type:\"}));\n\n allowedChartTypes.forEach(chartType => {\n controls.appendChild(coreFunctions.createElementHelper(\"input\", { type: \"radio\", id: `${subsection}-chart-${chartType}`, class: \"chart-type-radio\", name: `${subSubsection}-chart-type`, value: chartType, checked: (chartType == modalChartType), \"data-subsection\": subsection, \"data-subsubsection\": subSubsection }));\n controls.appendChild(coreFunctions.createElementHelper(\"label\", { for: `${subsection}-chart-${chartType}`, innerHTML: `${chartType}chart` }));\n });\n\n document.querySelector(`.subsection[data-section='${subsection}'] .subSubsectionControls`).appendChild(controls);\n }\n }\n\n removeSubSubsectionChartTypesControl(subsection) {\n let holder;\n if (holder = document.getElementById(`${subsection}-chart-type-control`)) {\n holder.remove();\n }\n }\n\n chartTypeSubSubsectionChange(element) {\n const chartType = element.value;\n const subsection = element.dataset.subsection;\n const subSubsection = element.dataset.subsubsection;\n const widgets = this.fields.filter(element => element.subsection == subsection && element.subSubsection == subSubsection);\n widgets.forEach(widget => {widget.checkForChartRefresh(chartType)});\n }\n\n createAnalysisSummaryBox() {\n\n let listSummaryPanel, listSummaryWrapper, contentHolder, listSummaryHeader, listSummaryExplanationHolder, listSummaryTooltipText;\n\n listSummaryTooltipText = \"Summary statistics should not be taken at face value. These statistics require interpretation and come with some caveats.
The statistics do not take into account group structure, splitting turnover/employees by location etc.
Why the summary statistics can be misleading
\";\n\n listSummaryPanel = coreFunctions.createElementHelper(\"div\", { class: \"listSummaryPanel\" });\n document.querySelector(`#listSummaryHolder .subsection[data-section='Summary'] .mainDataHolder`).appendChild(listSummaryPanel);\n\n listSummaryWrapper = coreFunctions.createElementHelper(\"div\", { class: \"listSummaryWrapper\" });\n listSummaryPanel.appendChild(listSummaryWrapper);\n\n contentHolder = coreFunctions.createElementHelper(\"div\", { class: \"barchartHolder\" });\n listSummaryWrapper.appendChild(contentHolder);\n\n listSummaryHeader = contentHolder.appendChild(coreFunctions.createElementHelper(\"h3\", {class:\"analysis-summary-title\"}))\n listSummaryHeader.appendChild(coreFunctions.createElementHelper(\"span\", { class: \"widgetTitle\", innerHTML: \"Analysis summary\" }));\n\n listSummaryExplanationHolder = listSummaryHeader.appendChild(coreFunctions.createElementHelper(\"span\", {class: \"widgetControls\"})).appendChild(coreFunctions.createElementHelper(\"span\", { class: \"widgetExplanationHolder\" }));\n\n listSummaryExplanationHolder.appendChild(coreFunctions.createElementHelper(\"input\", { type: \"checkbox\", id: \"listSummaryExplanationCheckbox\" })); // Checkbox\n\n listSummaryExplanationHolder.appendChild(coreFunctions.createElementHelper(\"label\", {id: \"listSummaryExplanationIcon\", class: `${(this.filtersObject.generalFilters.filterGroupIsActive(\"locations\")) ? \"flashExplanation\" : \"\"}`, for: \"listSummaryExplanationCheckbox\", innerHTML: `? ` }, {click: function(event) { this.classList.remove(\"flashExplanation\");}})); // Label with explanation element can be toggled on and off\n\n this.addResultsToAnalysisSummary(contentHolder);\n\n return this;\n }\n\n addResultsToAnalysisSummary(contentHolder) {\n let locationAnalysisField, filteredLocations, totalTurnoverSummary, totalEmployeesSummary, gvaSummary, womanLedSummary, anomaliesSummary, tradeDataSummary, locationFieldSum;\n\n const currencyFormat = new Intl.NumberFormat('en-EN', { style: 'currency', maximumFractionDigits: 0, currency: 'GBP' });\n const percentFormat = new Intl.NumberFormat('en', { style: 'percent', signDisplay: 'exceptZero', minimumSignificantDigits: 2});\n\n filteredLocations = this.getActiveLocationFilters();\n\n contentHolder.appendChild(coreFunctions.createElementHelper(\"div\", { class: \"analysisSummaryElement\", innerHTML: `The best estimate of the annual growth rate of the number of employees of all companies in the list.
Find out more here.
We have produced an estimated GVA measure at the company level using official GVA and employment data.
You can read more here
\";\n\n gvaSummary = contentHolder.appendChild(coreFunctions.createElementHelper(\"div\", { class: \"analysisSummaryElement\", innerHTML: `These are companies where there are more women directors than men directors that are active at the company.
Find out more here.
';\n\n womanLedSummary = contentHolder.appendChild(coreFunctions.createElementHelper(\"div\", { class: \"analysisSummaryElement\", innerHTML: `${element.label}${element.explanation}
` : \"\"),\n \"\",\n ); // Hide widget title if tooltip is just a link to the knowledge base, no extra text\n if (tooltip.length) {\n holder.appendChild(coreFunctions.createTooltipElement(tooltip, `${subSubsection}-tooltip`));\n }\n }\n }\n}\n\nclass AnalysisField {\n constructor(field, label, subsection, dataType, explanation, chartType, allowedChartTypes, listID, thingColumnLabel, emptyFieldLabel, displayDataAsPercentage, locationFilterField, listTitle, parent, subSubsection, stackedBarchartOptions) {\n this.field = field;\n this.label = label;\n this.subsection = subsection;\n this.dataType = dataType;\n this.explanation = explanation;\n this.fieldElements = [];\n this.perCapitaElements = [];\n this.perCapitaMultiplier = 1000;\n this.topCompanies = new TopCompanies(field, label, dataType);\n this.widget = null;\n this.chartType = chartType;\n this.allowedChartTypes = allowedChartTypes;\n this.listID = listID;\n this.thingColumnLabel = thingColumnLabel;\n this.emptyFieldLabel = emptyFieldLabel;\n this.totalCount = null;\n this.locationQuotientElements = [];\n this.displayDataAsPercentage = displayDataAsPercentage;\n this.locationFilterField = locationFilterField; // The corresponding location filter field for this geography if available\n this.listTitle = listTitle;\n this.parent = parent;\n this.subSubsection = subSubsection;\n this.bubbleMap;\n this.stackedBarchartOptions = stackedBarchartOptions;\n this.chart;\n }\n\n addTotalCount(total) {\n this.totalCount = total;\n return this;\n }\n\n sumOverElements(elements) {\n const fieldsToCheck = ['id', 'thing'];\n return this.fieldElements.filter(element => fieldsToCheck.some(i => elements.includes(element[i]))).reduce((accumulator, element) => accumulator + element.count, 0);\n }\n\n dataHasLocationFields(element) {\n return (element.hasOwnProperty(\"Latitude\") && element.hasOwnProperty(\"Longitude\"));\n }\n\n addCompanyActiveDatesElements(elements) {\n const rticsFilterIsActive = this.parent.filtersObject.rticFilters.rticsFilterIsActive();\n this.fieldElements = elements.map(element => new CompanyActiveDate(element.Year, element.Founded_Count, rticsFilterIsActive ? 0 : element.Died_Count, rticsFilterIsActive ? (element.ActiveCount + (element.Died_Count ?? 0)) : element.ActiveCount));\n return this;\n }\n\n addJobPostingInsightsElements(elements) {\n this.fieldElements = elements.map(element => new JobPostingInsight(this.field, element.SOC4Code, element.SOC4Name, element.Postings, element.Median_Salary, element.JobPostingsAndSalariesTimeSeries));\n return this;\n }\n\n addAnalysisFieldElements(data, nameField) {\n // TODO new ID field would go in here\n let analysisFieldObject = this;\n if (data.length > 0) {\n nameField = (data[0].Name) ? \"Name\" : nameField; // Check for existence of name field, use this by default. Otherwise set from function argument likely to be \"Thing\"\n let fieldHasLocationData = analysisFieldObject.dataHasLocationFields(data[0]);\n data.forEach(element => {if (element[nameField] != undefined && element[nameField] != \"\") {analysisFieldObject.newAnalysisFieldElement(analysisFieldObject.field, element[nameField], element.Count, (fieldHasLocationData) ? element.Latitude : null, (fieldHasLocationData) ? element.Longitude : null, analysisFieldObject.dataType, analysisFieldObject.thingColumnLabel, element.Quotient ?? null, element.ID ?? null)}});\n }\n return this;\n }\n\n newAnalysisFieldElement(field, thing, count, latitude, longitude, dataType, thingColumnLabel, quotient, id) {\n let analysisFieldElement = new AnalysisFieldElement(field, thing, count, latitude, longitude, dataType, thingColumnLabel, quotient, id);\n this.fieldElements.push(analysisFieldElement);\n return analysisFieldElement;\n }\n\n addPerCapitaFieldElements(data, nameField) {\n let analysisFieldObject = this;\n if (data.length > 0) {\n let fieldHasLocationData = analysisFieldObject.dataHasLocationFields(data[0]);\n data.forEach(element => analysisFieldObject.newPerCapitaElement(analysisFieldObject.field, element[nameField], element.Count, (fieldHasLocationData) ? element.Latitude : null, (fieldHasLocationData) ? element.Longitude : null, analysisFieldObject.dataType, analysisFieldObject.thingColumnLabel));\n }\n return this;\n }\n\n newPerCapitaElement(field, thing, count, latitude, longitude, dataType, thingColumnLabel) {\n let analysisFieldElement = new AnalysisFieldElement(field, thing, count, latitude, longitude, dataType, thingColumnLabel);\n this.perCapitaElements.push(analysisFieldElement);\n return analysisFieldElement;\n }\n\n fieldHasLocationQuotientData() {\n // Field has data values with location quotient values\n return (this.fieldElements.find(element => element.quotient != undefined) != undefined);\n }\n\n fieldHasIdData() {\n return this.fieldElements.find(element => element.id) != undefined;\n }\n\n addAnalysisFinancialFieldElements(measuredYearValues, projectedYearValues) {\n let analysisFieldObject = this;\n let measuredYears = [];\n measuredYearValues.forEach((measuredYear) => {\n let year, measuredYearValue, measuredYearCompanies, projectedYearValue, projectedYearCompanies;\n year = measuredYear.Year;\n measuredYears.push(year);\n measuredYearValue = measuredYear.Number;\n measuredYearCompanies = measuredYear.Companies;\n\n const projectedYearSearch = projectedYearValues.find(projectedYear => projectedYear.Year == year);\n projectedYearValue = (projectedYearSearch) ? projectedYearSearch.Number : null;\n projectedYearCompanies = (projectedYearSearch) ? projectedYearSearch.Companies : null;\n analysisFieldObject.newAnalysisFinancialFieldElement(year, measuredYearValue, measuredYearCompanies, projectedYearValue, projectedYearCompanies, analysisFieldObject.dataType);\n });\n\n projectedYearValues.forEach((projectedYear) => {\n if (!measuredYears.includes(projectedYear.Year)) {\n analysisFieldObject.newAnalysisFinancialFieldElement(projectedYear.Year, null, null, projectedYear.Number, projectedYear.Companies, analysisFieldObject.dataType);\n }\n });\n\n this.sortByField(\"year\", \"desc\");\n return analysisFieldObject;\n }\n\n sortByField(field, sort) {\n this.fieldElements = this.fieldElements.sort((a, b) => a[field]-b[field]);\n // Sort in descending order\n if (sort && sort == \"desc\") {\n this.fieldElements.reverse();\n }\n return this;\n }\n\n newAnalysisFinancialFieldElement(year, measuredYearValue, measuredYearCompanies, projectedYearValue, projectedYearCompanies, dataType) {\n let analysisFieldElement = new AnalysisFinancialFieldElement(year, measuredYearValue, measuredYearCompanies, projectedYearValue, projectedYearCompanies, dataType);\n this.fieldElements.push(analysisFieldElement);\n return analysisFieldElement;\n }\n\n addPercentageFieldElements() {\n return this;\n }\n\n hasLocationData() {\n return (this.fieldElements.length > 0 && this.fieldElements[0].latitude != undefined && this.fieldElements[0].longitude != undefined);\n }\n\n // Check if subSubsector is currently active or section does not have any subsectors\n addAnalyseFieldToPage() {\n let subSubsectionSelectElement = document.querySelector(`select.subSubsectionSelect[data-subsection='${this.subsection}']`);\n return (!this.subSubsection || (subSubsectionSelectElement && subSubsectionSelectElement.value == this.subSubsection));\n }\n\n createAnalysisWidget() {\n if (this.hasLocationData()) {\n // this.createLocationWidget().createLocationQuotientControl().createDataChart().createMap();\n this.createGeneralWidget().createLocationQuotientControl().createDataChart().createMap();\n } else {\n this.createGeneralWidget().createLocationQuotientControl().createAbsolutePercentageDataToggleControl().createDataChart();\n this.topCompanies.addTopCompaniesToPage();\n }\n return this;\n }\n\n createTopCompanies(topCompanies, allowZeroValues) {\n if (topCompanies) {\n this.topCompanies.addTopCompanies(topCompanies, allowZeroValues);\n }\n return this;\n }\n\n createLocationWidget() {\n let subsection, locationContentContainer, locationWidgetHolder, locationWidgetWrapper, locationData;\n subsection = document.querySelector(`#listSummaryHolder .subsection[data-section='${this.subsection}']`);\n\n locationContentContainer = subsection.querySelector(\".locationContentContainer\");\n if (locationContentContainer == undefined) {\n locationContentContainer = coreFunctions.createElementHelper(\"div\", {class: \"locationContentContainer\"});\n subsection.insertBefore(locationContentContainer, subsection.lastChild);\n }\n\n locationWidgetHolder = coreFunctions.createElementHelper(\"div\", { class: \"locationDataHolder\", id: `${this.field}Widget`, \"data-subsection\": this.subsection, \"data-subsubsection\": this.subSubsection });\n\n locationWidgetWrapper = coreFunctions.createElementHelper(\"div\", { class: \"locationDataWrapper\" });\n locationWidgetHolder.appendChild(locationWidgetWrapper);\n locationWidgetWrapper.appendChild(this.createWidgetTitleBar());\n\n locationData = coreFunctions.createElementHelper(\"div\", { class: \"locationData\" });\n locationWidgetWrapper.appendChild(locationData);\n locationData.appendChild(this.createLocationWidgetVisualisationHolder(false));\n locationData.appendChild(this.createLocationWidgetVisualisationHolder(true));\n locationContentContainer.appendChild(locationWidgetHolder);\n return this;\n }\n\n createLocationWidgetVisualisationHolder(mapVisualisation) {\n let listSummaryPanel;\n listSummaryPanel = coreFunctions.createElementHelper(\"div\", { class: \"listSummaryPanel\" });\n listSummaryPanel.appendChild(coreFunctions.createElementHelper(\"div\", { class: \"listSummaryWrapper\" })).appendChild(coreFunctions.createElementHelper(\"div\", { class: `barchartHolder${(mapVisualisation) ? \" mapHolder\" : \"\"}`, field: this.field, \"data-chartgroup\": this.subSubsection ?? this.field }));\n if (!mapVisualisation) {\n listSummaryPanel.querySelector(\".barchartHolder\").appendChild(coreFunctions.createElementHelper(\"div\", {class: \"chartContentHolder\", \"data-field\": this.field}));\n }\n return listSummaryPanel;\n }\n\n createGeneralWidget() {\n let listSummaryPanel, listSummaryWrapper, barchartHolder;\n\n listSummaryPanel = coreFunctions.createElementHelper(\"div\", { class: \"listSummaryPanel\", field: this.field, id: `${this.field}Widget`, \"data-subsection\": this.subsection, \"data-subsubsection\": this.subSubsection });\n document.querySelector(`#listSummaryHolder .subsection[data-section='${this.subsection}'] .mainDataHolder`).appendChild(listSummaryPanel);\n\n // If multiple charts displayed simultaneously make the widget full width. Display charts side by side\n if (Array.isArray(this.chartType)) {\n listSummaryPanel.classList.add(\"full-width\");\n }\n\n listSummaryWrapper = coreFunctions.createElementHelper(\"div\", { class: \"listSummaryWrapper\" });\n listSummaryPanel.appendChild(listSummaryWrapper);\n\n // TODO change this to a new class\n barchartHolder = coreFunctions.createElementHelper(\"div\", { class: \"barchartHolder\", field: this.field, \"data-chartgroup\": this.subSubsection ?? this.field }); // Add group attribute to chart based on subsubsection if available\n listSummaryWrapper.appendChild(barchartHolder);\n\n barchartHolder.appendChild(this.createWidgetTitleBar());\n barchartHolder.appendChild(coreFunctions.createElementHelper(\"div\", {class: \"chartContentHolder\", \"data-field\": this.field}));\n return this;\n }\n\n createWidgetControlsElements() {\n let insightsWidgetActionsControlHolder, insightsWidgetActionControlLabel, insightsWidgetActionControlLabelToggle, actionsHolder;\n const analyisFieldObject = this;\n\n if (this.subSubsection && this.allowedChartTypes.length > 1) {\n this.parent.createSubSubsectionChartTypesControl(this.subsection, this.subSubsection);\n }\n\n insightsWidgetActionsControlHolder = coreFunctions.createElementHelper(\"span\", { class: \"insightsActionsControlHolder\" });\n insightsWidgetActionsControlHolder.appendChild(coreFunctions.createElementHelper(\"button\", { innerHTML: \" \", class: \"actionControl insights-download\", field: this.field, title: \"Download analysis data\" }, { click: function (e) { analyisFieldObject.downloadAnalysisData() } }));\n\n\n // insightsWidgetActionsControlHolder.appendChild(coreFunctions.createElementHelper(\"input\", { class: \"insightsActionsControl\", type: \"checkbox\", id: `insightsActionsControl-${this.field}`, field: this.field })); // Checkbox element\n\n // insightsWidgetActionControlLabel = coreFunctions.createElementHelper(\"label\", { class: \"insightsActionsControlLabel\", for: `insightsActionsControl-${this.field}` });\n // insightsWidgetActionsControlHolder.appendChild(insightsWidgetActionControlLabel); // Label element for content\n\n // insightsWidgetActionControlLabelToggle = coreFunctions.createElementHelper(\"span\", { \"class\": \"controlsToggle\", innerHTML: \" \" });\n // insightsWidgetActionControlLabel.appendChild(insightsWidgetActionControlLabelToggle);\n\n // actionsHolder = coreFunctions.createElementHelper(\"span\", { class: \"controls\" });\n // insightsWidgetActionControlLabelToggle.appendChild(actionsHolder);\n\n // actionsHolder.appendChild(coreFunctions.createElementHelper(\"a\", { innerHTML: \"View list\", class: \"actionControl actions-view-list\", href: `${urlPath}${(this.listID == \"All\") ? `/explore?filters_code=${this.parent.filtersObject.storedFiltersCode}` : `/ml_list?list_id=${this.listID}`}` })); // View list action\n\n // actionsHolder.appendChild(coreFunctions.createElementHelper(\"button\", { innerHTML: \"Download\", class: \"actionControl actions-download\", field: this.field, title: \"Download analysis data\" }, { click: function (e) { analyisFieldObject.downloadAnalysisData() } })); // Download analysis data\n\n // // Multiple chart types are available\n // if (this.allowedChartTypes.length > 1) {\n // actionsHolder.appendChild(coreFunctions.createElementHelper(\"button\", { innerHTML: \"Edit widget\", class: \"actionControl actions-edit\", field: this.field, title: \"Edit widget\" }, { click: function (e) { analyisFieldObject.prepareEditWidgetControls() } }));\n // }\n return insightsWidgetActionsControlHolder;\n }\n\n createWidgetExplanationElement() {\n let widgetExplanationHolder = coreFunctions.createElementHelper(\"span\", { class: \"widgetExplanationHolder\" });\n\n widgetExplanationHolder.appendChild(coreFunctions.createElementHelper(\"input\", { type: \"checkbox\", id: `widgetExplanationCheckbox${this.field}` })); // Checkbox\n\n widgetExplanationHolder.appendChild(coreFunctions.createElementHelper(\"label\", { for: `widgetExplanationCheckbox${this.field}`, innerHTML: `? ` })); // Label with explanation element can be toggled on and off\n\n return widgetExplanationHolder;\n }\n\n createWidgetTitleBar() {\n let titleBar, widgetTitle, widgetControls;\n\n titleBar = coreFunctions.createElementHelper(\"h3\", {class: \"flex-row-holder align-center\"});\n const widgetTitleLabel = `${this.label}${(this.parent.listFiltersDescription) ? ` ${this.parent.listFiltersDescription}` : \"\"}`;\n widgetTitle = coreFunctions.createElementHelper(\"span\", { class: \"widgetTitle flex-grow\", innerHTML: widgetTitleLabel, title: this.label.replace(/(