diff --git a/SedimentDataExplorer.html b/SedimentDataExplorer.html new file mode 100644 index 0000000..fc676a3 --- /dev/null +++ b/SedimentDataExplorer.html @@ -0,0 +1,139 @@ + + + + + + Sediment Template Data Explorer + + + + +

Sediment Template Data Explorer - v0.20240203

+ Excel files - + + + + + + + +
+ Selection tools - + + + + + + + + +
+ Sheetanmes - + + + + + + + + + + + + + + +
+ +
+ Chart types - + + + + + + + + + + + + +
+ +
+ Location co-ordinate files - + + + + +
+ + Status - + + + + Load - + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SedimentDataExplorer.js b/SedimentDataExplorer.js new file mode 100644 index 0000000..8225926 --- /dev/null +++ b/SedimentDataExplorer.js @@ -0,0 +1,988 @@ + +// import {parse, stringify, toJSON, fromJSON} from 'flatted'; + const autocolors = window['chartjs-plugin-autocolors']; + Chart.register(autocolors); + const annotationPlugin = window['chartjs-plugin-annotation']; + Chart.register(annotationPlugin); + + markerPngs = ['marker-icon-red.png', 'marker-icon-orange.png', 'marker-icon-yellow.png', + 'marker-icon-green.png', 'marker-icon-blue.png', 'marker-icon-violet.png', + 'marker-icon-grey.png', 'marker-icon-gold.png','marker-icon-black.png']; + +// Define the projection for British National Grid (OSGB 1936) + proj4.defs("EPSG:27700", "+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +towgs84=446.448,-125.157,542.060,0.1502,0.2470,0.8421,-20.4894 +units=m +no_defs"); + + let lastInstanceNo = 0; + let noInstances = 16; + let chartInstance = []; + let instanceType = []; + let instanceSheet = []; + let highlighted = []; + for (i = 1; i < noInstances; i++) { + chartInstance[i] = null; + instanceType[i] = null; + instanceSheet[i] = null; + } + dataSheetNames = ['Physical Data','Trace metal data','PAH data','PCB data','BDE data','Organotins data','Organochlorine data']; + dataSheetNamesCheckboxes = []; + for (let i = 0; i < dataSheetNames.length; i++) { + dataSheetNamesCheckboxes[i] = dataSheetNames[i].replace(/\s/g, '').toLowerCase(); + } + sheetsToDisplay = {}; + for (i = 0; i < dataSheetNames.length; i++) { + sheetName = dataSheetNames[i]; + sheetsToDisplay[sheetName] = true; + } + subChartNames = ['samplegroup','chemicalgroup','gorhamtest','totalHC','congenertest'] + subsToDisplay = {}; + for (i = 0; i < subChartNames.length; i++) { + subName = subChartNames[i]; + subsToDisplay[sheetName] = true; + } + calcSheetNames = ['Physical Stats','PSA Charts','Metals calcs','PAH calcs','PCB calcs','BDE calcs','Organotin calcs','Organochlorine calcs']; + let map; // Declare map as a global variable + let fred; + let sampleMeasurements = {}; + let selectedSampleMeasurements = {}; + let sampleInfo = {}; + let selectedSampleInfo = {}; + let blankMeasurements = {}; + let namedLocations = {}; + //All actions level mg/kg + const actionLevels = {}; + actionLevels['Trace metal data'] = { + 'Arsenic (As)':[20,100], + 'Cadmium (Cd)': [0.4,5], + 'Chromium (Cr)': [40,400], + 'Copper (Cu)': [40,400], + 'Mercury (Hg)': [0.3,3], + 'Nickel (Ni)': [20,200], + 'Lead (Pb)': [50,500], + 'Zinc (Zn)': [130,800] + }; + actionLevels['Organotins data'] = { + 'Dibutyltine (DBT)': [0.1,1], + 'Tributyltin (TBT)': [0.1,1] + }; + actionLevels['PAH data'] = { + 'Acenapthene': [0.1,0], + 'Acenapthylene': [0.1,0], + 'Anthracene': [0.1,0], + 'Benz[a]anthracene': [0.1,0], + 'Benzo[a]pyrene': [0.1,0], + 'Benzo[b]fluoranthene': [0.1,0], + 'Benzo[g,h,i]perylene': [0.1,0], + 'Benzo[e]pyrene': [0.1,0], + 'Benzo[k]fluoranthene': [0.1,0], + 'C1-Napthalenes': [0.1,0], + 'C1-Phenanthrenes': [0.1,0], + 'C2-Napthalenes': [0.1,0], + 'C3-Napthalenes': [0.1,0], + 'Chrysene': [0.1,0], + 'Dibenz[a,h]anthracene': [0.01,0], + 'Fluoranthene': [0.1,0], + 'Fluorene': [0.1,0], + 'Indeno[123-c,d]pyrene': [0.1,0], + 'Napthalene': [0.1,0], + 'Perylene': [0.1,0], + 'Phenanthrene': [0.1,0], + 'Pyrene': [0.1,0] + }; + actionLevels['Organochlorine data'] = { + 'Dieldrin': [0.005,0], + 'Dichlorodiphenyltrichloroethane (PPDDT)': [0.001,0.2] + }; + let actionLevelColors = ['rgba(255, 255, 0, 1)','rgba(255, 0, 0, 0.5)']; + let actionLevelDashes = [[3,3],[5,5]]; + + firstTime = true; + + importData(); + + function saveSnapShot() { + const fileSave = document.getElementById('fileSave'); + const fileName = fileSave.value; + saveStatus(fileName); + } + + function loadSnapShotURL() { + const urlLoad = document.getElementById('urlLoad'); + const fileUrl = urlLoad.value; + loadStatus(fileUrl); + } + + function loadSnapShotFile() { + const fileInput = document.getElementById('fileLoad'); + const file = fileInput.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = function (e) { + // Read file as text + const textData = e.target.result; + // Decode the base64 data (if it was encoded) + const decodedData = decodeURIComponent(escape(atob(textData))); + // Parse the JSON data + const jsonData = JSON.parse(decodedData); + // Use the loaded data + // Now jsonData contains the loaded data + sampleInfo = jsonData.sampleInfo; + sampleMeasurements = jsonData.sampleMeasurements; + selectedSampleInfo = jsonData.selectedSampleInfo; + selectedSampleMeasurements = jsonData.selectedSampleMeasurements; + updateChart(); + }; + + reader.readAsText(file); + } + } + + function saveStatus(fileName) { + // Save data to a file + const dataBlob = new Blob([btoa(unescape(encodeURIComponent(JSON.stringify({ sampleInfo, sampleMeasurements, selectedSampleInfo, selectedSampleMeasurements }))))], { type: 'application/octet-stream' }); + const downloadLink = document.createElement('a'); + downloadLink.href = URL.createObjectURL(dataBlob); + downloadLink.download = fileName; + downloadLink.click(); + } + + // Function to load data from a file URL + async function loadStatus(fileURL) { + try { + // Fetch the data from the URL + const response = await fetch(fileURL); + + // Check if the fetch was successful (status code 200) + if (!response.ok) { + throw new Error(`Failed to fetch data. Status: ${response.status}`); + } + + // Read the response as text + const textData = await response.text(); + + // Decode the base64 data (if it was encoded) + const decodedData = decodeURIComponent(escape(atob(textData))); + + // Parse the JSON data + const jsonData = JSON.parse(decodedData); + + // Now jsonData contains the loaded data + sampleInfo = jsonData.sampleInfo; + sampleMeasurements = jsonData.sampleMeasurements; + selectedSampleInfo = jsonData.selectedSampleInfo; + selectedSampleMeasurements = jsonData.selectedSampleMeasurements; + updateChart(); + return jsonData; + } catch (error) { + console.error('Error loading data:', error.message); + } + } + + function clearData() { + sampleMeasurements = {}; + selectedSampleMeasurements = {}; + sampleInfo = {}; + selectedSampleInfo = {}; +/* if (map) { + map.remove(); + }*/ + const canvas = []; + for (i = 1; i < noInstances; i++) { + canvas[i] = document.getElementById('chart' + i); + clearCanvasAndChart(canvas[i], i); + } + } + + function importLocations() { + const fileInput = document.getElementById('fileLocations'); + const urlInput = document.getElementById('urlLocations'); + const files = fileInput.files; // Files is now a FileList object containing multiple files + const urls = urlInput.value.trim().split(',').map(url => url.trim()); // Split comma-separated URLs + + if (files.length === 0 && urls.length === 0) { + alert('Please select files or enter URLs.'); + return; + } + // Process files + for (let i = 0; i < files.length; i++) { + filename = files[i].name; + const reader = new FileReader(); + reader.onload = function (e) { + const data = new Uint8Array(e.target.result); + processExcelLocations(data,filename); + }; + reader.readAsArrayBuffer(files[i]); + } + + // Process URLs only if URLs are supplied + if (urls.length > 0) { + urls.forEach(url => { + // Check if the URL is a valid URL before fetching + if (!/^https?:\/\//i.test(url)) { + console.error('Invalid URL:', url); + return; + } + + fetch(url) + .then(response => response.arrayBuffer()) + .then(data => { + processExcelLocations(new Uint8Array(data),url); + }) + .catch(error => { + console.error('Error fetching the locations file:', error); + }); + }); + } + // Clear the input field after reading locations + fileInput.value = ''; + urlInput.value = ''; + } + + function processExcelLocations(data,url) { + // Based on simple Excel data in first sheet + // row 1 column titles + // column 1 location as per name used as sample in MMO templates + // column 2 latitude in decimal degrees + // column 3 longitude in decimal degrees +// console.log('processexcellocations',url); +//console.log('prcoessing ',url); + const workbook = XLSX.read(data, { type: 'array' }); +//console.log(workbook); + sheetData = workbook.Sheets['Sheet1']; +//console.log(sheetData); + const df = XLSX.utils.sheet_to_json(sheetData, { header: 1 }); + for (let r = 1; r < df.length; r++) { + const sample = df[r][0]; + namedLocations[sample] = {}; + namedLocations[sample].latitude = df[r][1]; + namedLocations[sample].longitude = df[r][2]; +console.log(sample,namedLocations[sample]); + } + } + + function importData() { + urls = {}; + if (firstTime) { + firstTime = false; + files = {}; + // Get the current URL + const currentURL = window.location.href; + + // Parse the URL to get the search parameters + const suppliedParams = new URLSearchParams(window.location.search); + // Get the value of the 'file' parameter + const statusParam = suppliedParams.get('status'); + if (statusParam) { + loadStatus(statusParam); + } else { + const urlParam = suppliedParams.get('urls'); + if (urlParam) { + urls = urlParam.split(',').map(url => url.trim()); // Split comma-separated URLs + } + } + const selParam = suppliedParams.get('selcharts'); + if (selParam) { + selcharts = selParam.split(',').map(sel => sel.trim()); // Split comma-separated URLs + if (selcharts) { + // Blank all the checkboxes + for (let i = 0; i < dataSheetNamesCheckboxes.length; i++) { +console.log(i,dataSheetNamesCheckboxes[i]); + const checkbox = document.getElementById(dataSheetNamesCheckboxes[i]); + checkbox.checked = false; + } + // Check all the boxes set in url + for (let i = 0; i < selcharts.length; i++) { + const checkbox = document.getElementById(selcharts[i]); + checkbox.checked = true; + } + } + } + const subParam = suppliedParams.get('subcharts'); + if (subParam) { + subcharts = subParam.split(',').map(subch => subch.trim()); // Split comma-separated URLs + if (subcharts) { + // Blank all the checkboxes + for (let i = 0; i < subChartNames.length; i++) { + const checkbox = document.getElementById(subChartNames[i]); + checkbox.checked = false; + } + // Check all the boxes set in url + for (let i = 0; i < subcharts.length; i++) { + const checkbox = document.getElementById(subcharts[i]); + checkbox.checked = true; + } + } + } + + } else { + const fileInput = document.getElementById('fileInput'); + const urlInput = document.getElementById('urlInput'); + files = fileInput.files; // Files is now a FileList object containing multiple files + urls = urlInput.value.trim().split(',').map(url => url.trim()); // Split comma-separated URLs + } + if (files.length === 0 && urls.length === 0) { + alert('Please select files or enter URLs.'); + return; + } + // Process files + for (let i = 0; i < files.length; i++) { + const filename = files[i].name; +console.log(filename); + const reader = new FileReader(); + + reader.onload = function (e) { + const data = new Uint8Array(e.target.result); + processExcelData(data,filename); + }; + reader.readAsArrayBuffer(files[i]); + } + + // Process URLs only if URLs are supplied + if (urls.length > 0) { + urls.forEach(url => { + // Check if the URL is a valid URL before fetching + if (!/^https?:\/\//i.test(url)) { + console.error('Invalid URL:', url); + return; + } + + fetch(url) + .then(response => response.arrayBuffer()) + .then(data => { + processExcelData(new Uint8Array(data),url); + }) + .catch(error => { + console.error('Error fetching the file:', error); + }); + }); + } + // Clear the input field after reading data + fileInput.value = ''; + urlInput.value = ''; + } + + function processExcelData(data, url) { +// console.log('processexceldata',url); + const workbook = XLSX.read(data, { type: 'array' }); + + function extractDataFromSheet(sheetName, sheetData, dateSampled) { + if (sheetData === null || sheetData == undefined) { + return null; + } +//console.log('ext data ',sheetData); + const df = XLSX.utils.sheet_to_json(sheetData, { header: 1 }); +// const df = XLSX.utils.sheet_to_json(sheetData, { header: 1, cellText: true }); + let startRow = -1; + let startCol = -1; + let measurementUnit = 'Not set'; + let totalSum = 0; + meas = {}; + // Read in date of analysis +//console.log('df ', df); + if (df[18][1] === 'Date of analysis:') { + row = 18; + column = 2; + } else { + if (df[17][1] === 'Date of analysis:') { + row = 17; + column = 2; + } else { + if (df[18][2] === 'Date of analysis:') { + row = 18; + column = 3; + } else { + if (df[17][2] === 'Date of analysis:') { + row = 17; + column = 3; + } else { + row = null; + dataAnalysed = 'AD: missing'; + contractor = 'Not listed'; + } + } + } + } + dateAnalysed = 'AD: missing'; + contractor = 'Not listed'; + for (let cc = 0; cc < 30; cc++) { + for (let r = 0; r < 30; r++) { + const cellValue = df[r][cc]; +//console.log(sheetName,r,cc); + if (typeof cellValue === 'string' && cellValue.includes('Date of analysis')) { + dateAnalysed = parseDates(df[r][cc + 1])[0]; + contractor = df[r - 1][cc + 1]; + break; + } + } + } + + for (let r = 0; r < df.length; r++) { + for (let cc = 0; cc < df[r].length; cc++) { + const cellValue = df[r][cc]; + if (typeof cellValue === 'string' && cellValue.includes('Dredge Area')) { + c = cc - 1; + measurementUnit = df[r][c+4]; + corec = 0; + extraValue = df[r][c]; + if (typeof extraValue === 'string' && extraValue.includes('Laboratory sample number')) { +console.log('Lab sampl numb'); + corec = 0; + measurementUnit = df[r][c+4]; + } else { +console.log('No Lab sampl numb'); + corec = 0; + measurementUnit = df[r][c+3]; + } + //} +//console.log('unit ',measurementUnit); + if (!(sheetName === 'Physical Data')) { + if(!(sheetName === 'PCB data')) { + startRow = r + 2; + } else { + startRow = r + 3; + } + startCol = c; + for (let col = startCol + 1; col < df[startRow].length; col++) { + if(!(sheetName === 'PCB data')) { + chemical = df[startRow - 1][col]; + } else { + chemical = df[startRow - 2][col]; + congener = df[startRow - 1][col]; + } + if (chemical !== null && chemical !== undefined) { + if (!meas.chemicals){ + meas.chemicals = {}; + meas.total = {}; + if(sheetName === 'PCB data') { + meas.congeners = {}; + } + } + if (!meas.chemicals[chemical]) { + meas.chemicals[chemical] = {}; + if(sheetName === 'PCB data') { + meas.congeners[chemical] = congener; + } + } + for (let row = startRow; row < df.length; row++) { + sample = df[row][startCol+2]; //bodge to pick up sample id + // If sample id not present then use Laboratory sample number instead + if (sample == undefined || sample == null) { + sample = df[row][startCol]; + } + if (!(sample == undefined || sample == null)) { +//console.log(sample); + const concentration = df[row][col+corec]; + if(sample.includes('detection')) { + meas.chemicals[chemical][sample] = concentration; + //.push(parseFloat(concentration) || 0); + } else { + if (!meas.chemicals[chemical].samples) { + meas.chemicals[chemical].samples = {}; + } + if (!meas.chemicals[chemical].samples[sample]) { + meas.chemicals[chemical].samples[sample] = {}; + } + if (!meas.total[sample]) { + meas.total[sample] = 0; + } + if (concentration !== null && concentration !== undefined) { + meas.chemicals[chemical].samples[sample]= parseFloat(concentration); + meas.total[sample] += parseFloat(concentration) || 0; + totalSum += parseFloat(concentration) || 0; + } + } + } + } + } else if (sheetName === 'PAH data') { + hydrocarbon = df[startRow - 2][col]; + if (hydrocarbon !== null && hydrocarbon !== undefined) { + if (hydrocarbon === 'Total hydrocarbon content (mg/kg)') { + for (let row = startRow; row < df.length; row++) { + sample = df[row][startCol+2]; //bodge to pick up sample id + // If sample id not present then use Laboratory sample number instead + if (sample == undefined || sample == null) { + sample = df[row][startCol]; + } + if (!(sample == undefined || sample == null)) { + const concentration = df[row][col+corec]; + if(sample.includes('detection')) { + meas[sample] = concentration; + //.push(parseFloat(concentration) || 0); + } else { + if (!meas.totalHC) { + meas.totalHC = {}; + meas.totalHCUnit = hydrocarbon; + } + meas.totalHC[sample] = parseFloat(concentration); + } + } + } + } + } + } + } + } else { + measurementUnit = df[r][c+7]; + meas.samples = {}; + meas.sizes = []; + startRow = r + 3; + startCol = c; + for (let col = startCol + 7; col < df[startRow - 1].length; col++) { + meas.sizes.push(parseFloat(df[startRow - 2][col]) || 0); + for (let row = startRow; row < 38; row++) { + sample = df[row][startCol+2]; //bodge to pick up sample id + // If sample id not present then use Laboratory sample number instead + if (sample == undefined || sample == null) { + sample = df[row][startCol]; + } + if (!(sample == undefined || sample == null)) { +//console.log(sheetName, col, row, sample); + if (!meas.samples) { + meas.samples = {} + } + if (!meas.samples[sample]) { + meas.samples[sample] = {}; + meas.samples[sample].psd = []; + meas.samples[sample]['Visual Appearance'] = df[row][startCol+3]; + meas.samples[sample]['Total solids (% total sediment)'] = df[row][startCol+5]; + meas.samples[sample]['Organic matter (total organic carbon)'] = df[row][startCol+6]; + } + +//console.log(sample); + meas.samples[sample].psd.push(parseFloat(df[row][col]) || 0); +//console.log('meas.psd ',df[row][col]); + } + } + } +//console.log(sheetName, ' in else ','meas ', meas); + } + break; + } + } + if (startRow !== -1 && startCol !== -1) { + break; + } + } + meas['Date analysed'] = dateAnalysed; + meas['Unit of measurement'] = measurementUnit; + meas['Laboratory/contractor'] = contractor; + if(!totalSum>0 && !(sheetName === 'Physical Data')) { + return 'No data for ' + sheetName; + } +//console.log(sheetName, 'meas ', meas); + sampleMeasurements[dateSampled][sheetName] = meas; + const sums = {}; + if (sheetName === "PCB data") { + const ICES7 = ["2,2',5,5'-Tetrachlorobiphenyl","2,4,4'-Trichlorobiphenyl","2,2',3,4,4',5,5'-Heptachlorobiphenyl", + "2,2',4,4',5,5'-Hexachlorobiphenyl","2,2',3,4,4',5'-Hexachlorobiphenyl", + "2,3',4,4',5-Pentachlorobiphenyl","2,2',4,5,5'-Pentachlorobiphenyl"]; + + for (const chemical in meas.chemicals) { + for (const sample in meas.chemicals[chemical].samples) { + //console.log(chemical,sample); + if (!sums[sample]) { + sums[sample] = { + ICES7: 0, + All: 0 + }; + } + //console.log(meas[chemical][sample]); +// const congenerConcentration = meas.chemicals[chemical].samples[sample].reduce((acc, val) => acc + val, 0); + const congenerConcentration = meas.chemicals[chemical].samples[sample] || 0; + if (ICES7.includes(chemical)) { + sums[sample].ICES7 += congenerConcentration; + } + sums[sample].All += congenerConcentration; + } + } + sampleMeasurements[dateSampled][sheetName].congenerTest = sums; + } + if (sheetName === "PAH data") { + // Goring Test protocol here, but results stored by sample + const lmw = ['Acenaphthene', 'Acenaphthylene', 'Anthracene', 'Fluorene', 'C1-Naphthalenes', 'Naphthalene', 'Phenanthrene']; + const hmw = ['Benz[a]anthracene', 'Benzo[a]pyrene', 'Chrysene', 'Dibenz[a,h]anthracene', 'Fluoranthene', 'Pyrene']; + + for (const chemical in meas.chemicals) { + for (const sample in meas.chemicals[chemical].samples) { + //console.log(chemical,sample); + if (!sums[sample]) { + sums[sample] = { + lmwSum: 0, + hmwSum: 0 + }; + } + //console.log(meas[chemical][sample]); + if (lmw.includes(chemical)) { + const lmwConcentrationSum = meas.chemicals[chemical].samples[sample] || 0; + sums[sample].lmwSum += lmwConcentrationSum; + } else if (hmw.includes(chemical)) { + const hmwConcentrationSum = meas.chemicals[chemical].samples[sample] || 0; + sums[sample].hmwSum += hmwConcentrationSum; + } + } + } + sampleMeasurements[dateSampled][sheetName].gorhamTest = sums; + } + return dateAnalysed; + } + +function parseDates(dateString) { +// Check if the date field is empty +if (!dateString) { + return ['Missing']; +} + +const dates = []; + +// Split the input by commas or hyphens +const dateParts = dateString.split(/,|-/); +dateParts.forEach(part => { + // Trim leading/trailing spaces + const trimmedPart = part.trim(); + + // Check if it's a range (contains a hyphen) + if (trimmedPart.includes('/')) { + const ukDate = convertToUKFormat(trimmedPart); + if (ukDate) { + dates.push(ukDate); + } + } else { + // Single date + const ukDate = convertToUKFormat(trimmedPart); + if (ukDate) { + dates.push(ukDate); + } + } +}); + +return dates.length > 0 ? dates : ['Missing']; +} + +function convertToUKFormat(dateString) { +const parts = dateString.split('/'); +if (parts.length === 3) { + // Assuming the format is mm/dd/yy + const mm = parts[0].padStart(2, '0'); + const dd = parts[1].padStart(2, '0'); + const yy = parts[2].padStart(2, '0'); + + // Construct the UK format: dd/mm/yy + return `${yy}/${mm}/${dd}`; +} + +// Return null for invalid date formats +return null; +} + + function extractApplicationDataFromSheet(sheetName, sheetData, url) { +// console.log('extractappdata',url); + const df = XLSX.utils.sheet_to_json(sheetData, { header: 1 }); +// const df = XLSX.utils.sheet_to_json(sheetData, { header: 1, cellText: true }); + + +// console.log(sheetName); //Output each cell value to console +// console.log(df.length); + let startRow = -1; + let startCol = -1; + + // This code should have found Applicant: but doesn't so bodged above +/* if (typeof cellValue === 'string' && cellValue.includes('Applicant:')) { + console.log('found applicant'); + const applicant = df[r][c+1]; + const applicationNumber = df[r+1][c+1]; + const applicationTitle = df[r+2][c+1]; + const dateSampled = df[r+3][c+1]; + const samplingLocation = df[r+4][c+1]; + } +*/ + + +/* // This relies on template first page not changing at all + const applicant = df[14][4]; + const applicationNumber = df[15][4]; + const applicationTitle = df[16][4];*/ + for (i = 16; 19; i++) { + dateRow = i; + if (df[dateRow][2].includes('Date sampled:')) { + break; + } + dateRow = 0; +// dateSampled = 'SD: missing'; + } + if (dateRow > 0) { + applicant = df[dateRow-3][4]; + applicationNumber = df[dateRow-2][4]; + applicationTitle = df[dateRow-1][4]; + dateSampled = parseDates(df[dateRow][4])[0]; + } else { + applicant = df[14][4]; + applicationNumber = df[15][4]; + applicationTitle = df[16][4]; + dateSampled = 'SD: missing'; + dateRow = 17; + } + sampleInfo[dateSampled] = {}; + sampleInfo[dateSampled]['Applicant'] = applicant; + sampleInfo[dateSampled]['Application number'] = applicationNumber; + sampleInfo[dateSampled]['Application title'] = applicationTitle; + sampleInfo[dateSampled]['Date sampled'] = dateSampled; + sampleInfo[dateSampled]['fileURL'] = url; +console.log('extractapplicationdatafromsheet ', dateSampled,url); + sampleInfo[dateSampled].position = {}; + sampleMeasurements[dateSampled] = {}; + + const samplingLocation = df[dateRow + 1][4]; + for (let c = 0; c < df.length; c++) { + + //Bodge as didn't read enough of columns when set by length alone +// for (let r = 0; r < df[c].length; r++) { + if (df[c].length < 40 ) { + cdepth = 40; + } else { + cdepth = df[c].length; + } + for (let r = 0; r < cdepth; r++) { + const cellValue = df[r][c]; +// console.log(c,r,cellValue); +// if (typeof cellValue === 'string' && cellValue.includes('Excluded sample (MMO use)')) { + if (typeof cellValue === 'string' && cellValue.includes('Sample location (decimal degrees, WGS84)')) { +console.log('Sample Location'); + const extraValue = df[r][c-1] +console.log(extraValue); + if (typeof extraValue === 'string' && extraValue.includes('Excluded sample (MMO use)')) { + startRow = r + 2; + startCol = c-2; + samCol = startCol; + excCol = startCol + 1; + latCol = startCol + 2; + lonCol = startCol + 3; + namCol = startCol + 4; + depCol = startCol + 5; + dreCol = startCol + 6; + } else { + startRow = r + 2; + startCol = c-1; + samCol = startCol; + excCol = startCol + 6; // not present here + dreCol = startCol + 5; + latCol = startCol + 1; + lonCol = startCol + 2; + namCol = startCol + 3; + depCol = startCol + 4; + } + + for (let row = startRow; row < df.length; row++) { + const sample = df[row][samCol]; //bodge to pick up sample id + if (!(sample == undefined || sample == null)) { + sInfo = {}; + sInfo['Excluded sample (MMO use)'] = df[row][excCol]; + sInfo['Dredge area'] = df[row][dreCol]; + point = parseCoordinates(df[row][latCol],df[row][lonCol]); + if (point === null || point === undefined) { + // latitude and longitude aren't specified so try to retrieve latlon from previously entered locations + if (namedLocations[sample] !== null && namedLocations[sample] !== undefined) { + point = {}; + point['latitude'] = namedLocations[sample].latitude; + point['longitude'] = namedLocations[sample].longitude; + } else { +//console.log('lat and long undefined does not match', sample); + point = {}; + point['latitude'] = undefined; + point['longitude'] = undefined; + } + } + sInfo['Position latitude'] = point['latitude']; + sInfo['Position longitude'] = point['longitude']; + sInfo['Location name (as per sampling plan)'] = df[row][namCol]; + sInfo['Sampling depth (m)'] = processDepth(df[row][depCol]); + sInfo['Sampling location']= samplingLocation; + sampleInfo[dateSampled].position[sample] = sInfo; + } + } + break; + } + } + if (startRow !== -1 && startCol !== -1) { +// console.log('Breaking up'); + break; + } + } + return dateSampled; + } + + +// workbook.SheetNames.forEach(sheetName => { +// const sheetData = workbook.Sheets[sheetName]; +// extractDataFromSheet(sheetName, sheetData); +// }); + sheetName = 'Application info'; + sheetData = workbook.Sheets[sheetName]; + dateSampled = extractApplicationDataFromSheet(sheetName, sheetData, url); + sheetName = 'PAH data'; + sheetData = workbook.Sheets[sheetName]; + dateAnalysed = extractDataFromSheet(sheetName, sheetData, dateSampled); + if (dateSampled.includes('SD: Missing')) { + dateSampled = dateAnalysed + 'ADMSD'; +// sampleMeasurements[dateSampled] = sampleMeasurements['holder']; +// delete sampleMeasurements['holder']; +// sampleInfo[dateSampled] = sampleInfo['holder']; +// delete sampleInfo['holder']; + sampleMeasurements[dateSampled] = sampleMeasurements['SD: Missing']; + delete sampleMeasurements['SD: Missing']; + sampleInfo[dateSampled] = sampleInfo['SD: Missing']; + delete sampleInfo['SD: Missing']; + } else if (dateSampled > dateAnalysed) { + sampleMeasurements[dateAnalysed + 'ADWSD'] = sampleMeasurements[dateSampled]; + delete sampleMeasurements[dateSampled]; + sampleInfo[dateAnalysed + 'ADWSD'] = sampleInfo[dateSampled]; + delete sampleInfo[dateSampled]; + dateSampled = dateAnalysed + 'ADWSD'; + } + sheetName = 'PCB data'; + sheetData = workbook.Sheets[sheetName]; + dateAnalysed = extractDataFromSheet(sheetName, sheetData, dateSampled); + sheetName = 'Trace metal data'; + sheetData = workbook.Sheets[sheetName]; + dateAnalysed = extractDataFromSheet(sheetName, sheetData, dateSampled); + sheetName = 'BDE data'; + sheetData = workbook.Sheets[sheetName]; + dateAnalysed = extractDataFromSheet(sheetName, sheetData, dateSampled); + sheetName = 'Organotins data'; + sheetData = workbook.Sheets[sheetName]; + dateAnalysed = extractDataFromSheet(sheetName, sheetData, dateSampled); + sheetName = 'Organochlorine data'; + sheetData = workbook.Sheets[sheetName]; + dateAnalysed = extractDataFromSheet(sheetName, sheetData, dateSampled); + + sheetName = 'Physical Data'; + sheetData = workbook.Sheets[sheetName]; + dateAnalysed = extractDataFromSheet(sheetName, sheetData, dateSampled); + + + selectedSampleMeasurements = sampleMeasurements; + selectedSampleInfo = sampleInfo; + updateChart(); + }; + + function processDepth(enteredDepth) { +// console.log(enteredDepth); + if (!enteredDepth) { + return { + minDepth: 0.0, + maxDepth: 0.0, + }; + } + // Remove any non-numeric characters, except for dots and hyphens + const cleanedDepth = enteredDepth.replace(/[^0-9.\-]/g, ''); + + // Split the cleaned depth by hyphens to handle ranges + const depthParts = cleanedDepth.split('-'); + + // Convert the parts to numbers + const numericDepths = depthParts.map(parseFloat); + + // Determine min and max depths + const minDepth = Math.min(...numericDepths); + const maxDepth = Math.max(...numericDepths); + + // Return the result + return { + minDepth: isNaN(minDepth) ? 0.0 : minDepth, + maxDepth: isNaN(maxDepth) ? 0.0 : maxDepth, + }; + } + +// Function to create a new canvas for a chart +function createCanvas(instanceNo) { +const container = document.getElementById('chartContainer'); +const canvas = document.createElement('canvas'); +canvas.id = 'chart' + instanceNo; // Unique chart ID +container.appendChild(canvas); // Append the canvas to the container +} + +// Function to create a button for resetting zoom +function createResetZoomButton(chart,instanceNo) { +const button = document.createElement('button'); +button.id = 'button'+instanceNo +button.textContent = 'Reset Zoom'; +button.addEventListener('click', () => { + chart.resetZoom(); +}); +const container = document.getElementById('chartContainer'); +container.appendChild(button); +} + +function clearCanvasAndChart(canvas, chartInstanceNo) { + if (canvas) { + const ctx = canvas.getContext('2d'); + ctx.clearRect(0, 0, canvas.width, canvas.height); + if (chartInstance[chartInstanceNo]) { + chartInstance[chartInstanceNo].destroy(); + chartInstance[chartInstanceNo] = null; + } + /* // Hide the canvas + const convas = document.getElementById("chart" + chartInstanceNo); + convas.style.display = "none";*/ + // Remove the canvas + const convas = document.getElementById("chart" + chartInstanceNo); + convas.remove(); + // Remove a reset button if created + const buttonToRemove = document.getElementById('button' + chartInstanceNo); + // Check if the button exists + if (buttonToRemove) { + // Remove the button + buttonToRemove.remove(); + } else { + console.log('Button not found', chartInstanceNo); + } + } +} + +function chemicalTypeHasData(sheetName) { + chemicalTypeData = false; + for (const ds in selectedSampleMeasurements) { + const chemicalTypes = Object.keys(selectedSampleMeasurements[ds]); + //console.log(ds, sheetName, chemicalTypes); + if (chemicalTypes.includes(sheetName)) { + chemicalTypeData = true; + return chemicalTypeData; + } + } +} + +function filenameDisplay() { + + const fileDisplayDiv = document.getElementById("fileDisplay"); + // blank it each time + fileDisplayDiv.innerHTML = ""; + + iconNo = 0; + for (dateSampled in selectedSampleInfo) { + currentIcon = markerPngs[iconNo]; + iconNo = (iconNo + 1) % 9; + + const fileURL = sampleInfo[dateSampled].fileURL; + + // Create an image element for the icon + const iconElement = document.createElement("img"); + iconElement.src = currentIcon; + iconElement.alt = "Marker Icon"; + iconElement.style.width = "20px"; // Adjust the width as needed + + // Create a link element for each file and append it to the div + const linkElement = document.createElement("a"); + linkElement.href = fileURL; + // console.log(fileURL); + // console.log(dateSampled); + linkElement.textContent = `File for ${dateSampled}: ${fileURL}`; + linkElement.target = "_blank"; // Open link in a new tab/window + + // Append the icon before the link + fileDisplayDiv.appendChild(iconElement); + fileDisplayDiv.appendChild(linkElement); + + // Add a line break for better readability + fileDisplayDiv.appendChild(document.createElement("br")); + }; +} + + diff --git a/markers/marker-icon-black.png b/markers/marker-icon-black.png new file mode 100644 index 0000000..d262ae4 Binary files /dev/null and b/markers/marker-icon-black.png differ diff --git a/markers/marker-icon-blue.png b/markers/marker-icon-blue.png new file mode 100644 index 0000000..e2e9f75 Binary files /dev/null and b/markers/marker-icon-blue.png differ diff --git a/markers/marker-icon-gold.png b/markers/marker-icon-gold.png new file mode 100644 index 0000000..162fada Binary files /dev/null and b/markers/marker-icon-gold.png differ diff --git a/markers/marker-icon-green.png b/markers/marker-icon-green.png new file mode 100644 index 0000000..56db5ea Binary files /dev/null and b/markers/marker-icon-green.png differ diff --git a/markers/marker-icon-grey.png b/markers/marker-icon-grey.png new file mode 100644 index 0000000..ebbab8e Binary files /dev/null and b/markers/marker-icon-grey.png differ diff --git a/markers/marker-icon-orange.png b/markers/marker-icon-orange.png new file mode 100644 index 0000000..fbbce7b Binary files /dev/null and b/markers/marker-icon-orange.png differ diff --git a/markers/marker-icon-red.png b/markers/marker-icon-red.png new file mode 100644 index 0000000..3e64e06 Binary files /dev/null and b/markers/marker-icon-red.png differ diff --git a/markers/marker-icon-violet.png b/markers/marker-icon-violet.png new file mode 100644 index 0000000..28efc3c Binary files /dev/null and b/markers/marker-icon-violet.png differ diff --git a/markers/marker-icon-yellow.png b/markers/marker-icon-yellow.png new file mode 100644 index 0000000..b011eea Binary files /dev/null and b/markers/marker-icon-yellow.png differ diff --git a/markers/marker-shadow.png b/markers/marker-shadow.png new file mode 100644 index 0000000..9fd2979 Binary files /dev/null and b/markers/marker-shadow.png differ diff --git a/sdeCharts.js b/sdeCharts.js new file mode 100644 index 0000000..7bbc9c4 --- /dev/null +++ b/sdeCharts.js @@ -0,0 +1,971 @@ +function updateChart(){ + if (lastInstanceNo > 0) { + const canvas = []; + for (i = 1; i < lastInstanceNo + 1; i++) { + canvas[i] = document.getElementById('chart' + i); + clearCanvasAndChart(canvas[i], i); + } + } + const yScaleType = document.getElementById('scaleType').checked ? 'logarithmic' : 'linear'; // Check the checkbox state + for (i = 0; i < dataSheetNames.length; i++) { + sheetName = dataSheetNames[i]; + sheetsToDisplay[dataSheetNames[i]] = document.getElementById(dataSheetNamesCheckboxes[i]).checked ? true : false; // Check the checkbox state + } + for (i = 0; i < subChartNames.length; i++) { + subName = subChartNames[i]; + subsToDisplay[subName] = document.getElementById(subName).checked ? true : false; // Check the checkbox state + } + lastInstanceNo = 0; + blankSheets = {}; + setBlanksForCharting(selectedSampleMeasurements); + for (sheetName in sheetsToDisplay) { + if (sheetsToDisplay[sheetName] && chemicalTypeHasData(sheetName)) { + lastInstanceNo = displayCharts(sheetName, lastInstanceNo, yScaleType); + } + } +console.log('lastInstanceNo ',lastInstanceNo); + sampleMap(selectedMeas, yScaleType); + filenameDisplay(); +} + +function displayCharts(sheetName, instanceNo, yScaleType) { +// const { unitTitle, selectedMeas } = dataForCharting(selectedSampleMeasurements, sheetName); + if(sheetName === 'Physical Data') { + retData = dataForPSDCharting(selectedSampleMeasurements, sheetName); + unitTitle = retData['unitTitle']; +//console.log('unitTitle displayCharts ',unitTitle); + sizes = retData['sizes']; + selectedMeas = retData['measChart']; +//console.log(sizes); +//console.log('selectedMeas ', selectedMeas); + instanceNo += 1; + displayPSDChart(sizes, selectedMeas, sheetName, instanceNo, yScaleType, unitTitle); + } else { + retData = dataForCharting(selectedSampleMeasurements, sheetName); + unitTitle = retData['unitTitle']; +//console.log('unitTitle displayCharts ',unitTitle); + selectedMeas = retData['measChart']; + +//console.log('selectedMeas ', selectedMeas); + if (subsToDisplay['samplegroup']) { + instanceNo += 1; + displaySampleChart(selectedMeas, sheetName, instanceNo, yScaleType, unitTitle); + } + if (subsToDisplay['chemicalgroup']) { + instanceNo += 1; + displayChemicalChart(selectedMeas, sheetName, instanceNo, yScaleType, unitTitle); + } + if (sheetName === 'PAH data' && subsToDisplay['gorhamtest']) { + instanceNo += 1; + selectedSums = sumsForGorhamCharting(selectedSampleMeasurements); + displayGorhamTest(selectedSums, sheetName, instanceNo, yScaleType, unitTitle); + } + if (sheetName === 'PAH data' && subsToDisplay['totalHC']) { + instanceNo += 1; + retData = sumsForTotalHCCharting(selectedSampleMeasurements); + unitTitle = retData['unitTitle']; + selectedSums = retData['measChart']; + displayTotalHC(selectedSums, sheetName, instanceNo, yScaleType, unitTitle); + } + if (sheetName === 'PCB data' && subsToDisplay['congenertest']) { + instanceNo += 1; + selectedSums = sumsForCongenerCharting(selectedSampleMeasurements); + displayCongener(selectedSums, sheetName, instanceNo, yScaleType, unitTitle); + } + } + // Display the canvas +//console.log('Display the canvas ',instanceNo); + return instanceNo +} + +function dataForCharting(selected, sheetName) { + const datesSampled = Object.keys(selected); + ct = sheetName; + unitTitle = blankSheets[ct]['Unit of measurement']; + measChart = {}; +// for (const ds in selected) { + datesSampled.sort(); + datesSampled.forEach (ds => { + if (!(selected[ds][ct] == undefined || selected[ds][ct] == null)) { + for (const c in selected[ds][ct].chemicals) { + if (measChart[c] == undefined || measChart[c] == null) { + measChart[c] = {}; + } +// for (const s in selected[ds][ct].chemicals[c].samples) { + for (const s in selectedSampleInfo[ds].position) { + if (selected[ds][ct].chemicals[c].samples[s] == undefined || selected[ds][ct].chemicals[c].samples[s] == null) { + measChart[c][ds + ': ' + s] = 0.0; + } else { + measChart[c][ds + ': ' + s] = selected[ds][ct].chemicals[c].samples[s]; + } + } + } + } else { + // Have to deal with samples without measurements set everything to zero + for (const c in blankSheets[ct].chemicals) { + if (measChart[c] == undefined || measChart[c] == null) { + measChart[c] = {}; + } + for (const s in selectedSampleInfo[ds].position) { + measChart[c][ds + ': ' + s] = 0.0; + } + } + } + }); + unitTitle = blankSheets[ct]['Unit of measurement']; + return {unitTitle, measChart} +} + +function sumsForCongenerCharting(selected) { + const datesSampled = Object.keys(selected); + measChart = {}; +// for (const ds in selected) { + datesSampled.sort(); + datesSampled.forEach (ds => { + if (!(selected[ds]['PCB data'] == undefined || selected[ds]['PCB data'] == null)) { +// for (const s in selected[ds]['PCB data'].congenerTest) { + for (const s in selectedSampleInfo[ds].position) { +console.log(ds,s); + if (selected[ds]['PCB data'].congenerTest[s] == undefined || selected[ds]['PCB data'].congenerTest[s] == null) { + measChart[ds + ': ' + s] = { ICES7 : 0.0, All : 0.0 }; + } else { + measChart[ds + ': ' + s] = selected[ds]['PCB data'].congenerTest[s]; + } + } + } else { + for (const s in selectedSampleInfo[ds].position) { + measChart[ds + ': ' + s] = { All : 0.0, ICES7 : 0.0}; + } + } + }); + return measChart +} + +function sumsForGorhamCharting(selected) { + const datesSampled = Object.keys(selected); + measChart = {}; +// for (const ds in selected) { + datesSampled.sort(); + datesSampled.forEach (ds => { + if (!(selected[ds][ct] == undefined || selected[ds][ct] == null)) { + for (const s in selected[ds]['PAH data'].gorhamTest) { + if (selected[ds]['PAH data'].gorhamTest[s] == undefined || selected[ds]['PAH data'].gorhamTest[s] == null) { + measChart[ds + ': ' + s] = { hmwSum : 0.0, lmwSum : 0.0}; + } else { + measChart[ds + ': ' + s] = selected[ds]['PAH data'].gorhamTest[s]; + } + } + } else { + for (const s in selectedSampleInfo[ds].position) { + measChart[ds + ': ' + s] = { hmwSum : 0.0, lmwSum : 0.0}; + } + } + }); + return measChart +} + +function sumsForTotalHCCharting(selected) { + const datesSampled = Object.keys(selected); + unitTitle = blankSheets[ct]['totalHCUnit']; + measChart = {}; +// for (const ds in selected) { + datesSampled.sort(); + datesSampled.forEach (ds => { + if (!(selected[ds][ct] == undefined || selected[ds][ct] == null)) { + for (const s in selected[ds]['PAH data'].totalHC) { + if (selected[ds]['PAH data'].total[s] == undefined || selected[ds]['PAH data'].totalHC[s] == null) { + measChart[ds + ': ' + s] = { totalHC : 0.0, fractionPAH : 0.0}; + } else { +// measChart[ds + ': ' + s] = {totalHC : selected[ds]['PAH data'].totalHC[s], fractionPAH : (selected[ds]['PAH data'].total[s] / (1000 * selected[ds]['PAH data'].totalHC[s]))}; + measChart[ds + ': ' + s] = {totalHC : selected[ds]['PAH data'].totalHC[s], fractionPAH : selected[ds]['PAH data'].total[s] / 1000}; + } + } + } else { + for (const s in selectedSampleInfo[ds].position) { + measChart[ds + ': ' + s] = { totalHC : 0.0, fractionPAH : 0.0}; + } + } + }); + return {unitTitle, measChart} +} + + + +function setBlanksForCharting(selected) { + const datesSampled = Object.keys(selected); + // Have to deal with samples without measurements set everything to zero + for (const ds in selected) { + for (const ct in selected[ds]) { + if (blankSheets[ct] == null || blankSheets[ct] == undefined) { + if (!(selected[ds][ct] == undefined || selected[ds][ct] == null)) { + blankSheets[ct] = selected[ds][ct]; + } + } + } + } + return +} + +function dataForPSDCharting(selected, sheetName) { + const datesSampled = Object.keys(selected); + ct = sheetName; +// unitTitle = selected[datesSampled[0]][ct]['Unit of measurement']; + unitTitle = blankSheets[ct]['Unit of measurement']; + measChart = {}; + sizes = null; +// for (const ds in selected) { + datesSampled.sort(); + datesSampled.forEach (ds => { + if (!(selected[ds][ct] == undefined || selected[ds][ct] == null)) { + if (!sizes) { + sizes = selected[ds][ct].sizes; + sizes = sizes.map(phiSize => Math.pow(2, -phiSize)); + } + for (const s in selected[ds][ct].samples) { + measChart[ds + ': ' + s] = selected[ds][ct].samples[s].psd; + } + } else { + for (const s in selectedSampleInfo[ds].position) { + measChart[ds + ': ' + s] = new Array(42).fill(0.0); + } + } + }); +//console.log('dataforPSD ', unitTitle,sizes,measChart); + return {unitTitle, sizes, measChart} +} + +function displayPSDChart(sizes, meas, sheetName, instanceNo, yLogLin, unitTitle) { + createCanvas(instanceNo); + const convas = document.getElementById("chart" + instanceNo); + convas.style.display = "block"; + instanceType[instanceNo] = 'PSD'; + instanceSheet[instanceNo] = sheetName; + // Extract sample names from the PSD data structure + const sampleNames = Object.keys(meas); + + // Create datasets for each sample + const datasets = sampleNames.map((sampleName, index) => { + return { + label: sampleName, + data: meas[sampleName], +// borderColor: getRandomColor(), // Function to generate random color + borderWidth: 2, + fill: false, + }; + }); + + // Chart configuration + const chartConfig = { + type: 'line', + data: { + labels: sizes, + datasets: datasets, + }, + options: { + plugins: { + title: { + display: true, + text: sheetName + }, + legend: { + display: false, + position: 'bottom', + labels: { + font: { // Customize legend label font + size: 14, + weight: 'italic', + padding: 10 + } + } + }, + // Add a custom plugin for interactivity + selectSample: { + highlightedSample: null, + }, + }, + scales: { + x: { + type: 'logarithmic', + position: 'bottom', + title: { + display: true, + text: 'mm' + } + }, + y: { + beginAtZero: true, + type: yLogLin, + title: { + display: true, + text: unitTitle + } + } + }, + autocolors: { + mode: 'label' + } + } + }; +// }; + + +const ctx = document.getElementById('chart' + instanceNo).getContext('2d'); +chartInstance[instanceNo] = new Chart(ctx, chartConfig); + +Chart.register({ +id: 'selectSample', +afterDraw: function (chart, args, options) { + const highlightedSample = chart.options.plugins.selectSample.highlightedSample; + + if (highlightedSample) { +//console.log('highlightedSample ', highlightedSample); + const datasetIndex = chart.data.datasets.findIndex(dataset => dataset.label === highlightedSample); + + if (datasetIndex !== -1) { + const dataset = chart.data.datasets[datasetIndex]; + dataset.borderWidth = 4; + dataset.borderColor = 'red'; + } + } +}, +}); +} + +// Function to generate a random color +function getRandomColor() { +const letters = '0123456789ABCDEF'; +let color = '#'; +for (let i = 0; i < 6; i++) { +color += letters[Math.floor(Math.random() * 16)]; +} +return color; +} + +function displayPSDHighlight(meas, yLogLin, instanceNo, clickedMapSample) { + clickedSamples = findSamplesInSameLocation(clickedMapSample); + const allChemicals = Object.keys(meas); + let clickedIndexes = []; + clickedSamples.forEach (clickedSample => { + index = -1; + for (const sample in meas[allChemicals[0]]) { + index += 1; + if (sample.includes(clickedSample)) { + clickedIndexes.push(index); + } + } + }); + clickedIndexes.forEach(item => { +//console.log('displayPSDHighlight',clickedIndexes); +//console.log('item ',item); +chartInstance[instanceNo].options.plugins.selectSample.highlightedSample = item; + }); + // Update the chart + chartInstance[instanceNo].update(); +} + +// Helper function to remove highlighting +function removePSDHighlight() { +chartInstance.options.plugins.selectSample.highlightedSample = null; +chartInstance.update(); +} + +function displaySampleChart(meas, sheetName, instanceNo, yLogLin, unitTitle) { + createCanvas(instanceNo); + const convas = document.getElementById("chart" + instanceNo); + convas.style.display = "block"; + instanceType[instanceNo] = 'chemical'; + instanceSheet[instanceNo] = sheetName; + const allChemicals = Object.keys(meas); + const allSamples = Object.keys(meas[allChemicals[0]]); // Assuming all samples have the same chemicals + const datasets = allChemicals.map((chemical, index) => { + const data = allSamples.map(sample => meas[chemical][sample]); // Using the first concentration value for simplicity + return { + label: chemical, + data: data, + borderWidth: 1, + yAxisID: 'y', + }; + }); + displayAnyChart(meas, allSamples,datasets,instanceNo,sheetName,unitTitle,yLogLin); +} + +function highlightMapLocation(clickedIndex) { + console.log(clickedIndex); + return +} + +function displayAnyChart(meas, all, datasets, instanceNo, title, yTitle, yLogLin) { + //console.log('chartInstance ', instanceNo); + // const ctx = document.getElementById('chart' + instanceNo).getContext('2d'); + const ctx = document.getElementById('chart' + instanceNo); + //console.log(sheetName, instanceNo); + stanGraph = { + type: 'bar', + data: { + labels: all, + datasets: datasets + }, + options: { + interaction: { + mode: 'index', + axis: 'xy' + }, + plugins: { + title: { + display: true, + text: title + }, + legend: { + display: false, + position: 'top', + labels: { + font: { // Customize legend label font + size: 14, + weight: 'italic', + padding: 10 + } + } + }, + zoom: { + pan: { + // pan options and/or events + enabled: true, + mode: 'xy', + modifierKey: 'shift', + }, + limits: { + y: { min: 0 } + // axis limits + }, + zoom: { + // zoom options and/or events + wheel: { + enabled: true, + }, + drag: { + enabled: true, + }, + mode: 'xy' + } + }, + }, + indexAxis: 'x', + scales: { + x: { + beginAtZero: true, + ticks: { + maxRotation: 90, + minRotation: 90, + autoSkip: false, + } + }, + y: { + beginAtZero: true, + type: yLogLin, + title: { + display: true, + text: yTitle, + position: 'left', + } + }, + }, + autocolors: { + mode: 'label' + } + } + }; + if (title.includes('hydrocarbon')) { + stanGraph.options.scales.y1 = { + beginAtZero: true, + type: yLogLin, + position: 'right', + title: { + display: true, + text: 'Total PAHs as Fraction of Total Hydrocarbons', + position: 'right', + } + }; + console.log(stanGraph); + }; + chartInstance[instanceNo] = new Chart(ctx, stanGraph); + //clickableScales(chartInstance[instanceNo], 1); + function clickableScales(chart, canvas, click) { + //console.log(chart); + const height = chart.scales.x.height; + const top = chart.scales.x.top; + const bottom = chart.scales.x.bottom; + const left = chart.scales.x.left; + const right = chart.scales.x.maxWidth / chart.scales.x.ticks.length; + //console.log('click scales - height,top,bottom,left,right', height,top,bottom,left,right); + let resetCoordinates = canvas.getBoundingClientRect(); + //console.log('click - raw x y', click.clientX, click.clientY); + const x = click.clientX - resetCoordinates.left; + const y = click.clientY - resetCoordinates.top; + //console.log('click - corrrected x y', x, y); + //console.log('chart.scales.x.ticks.length',chart.scales.x.ticks.length); + if (y >= top && y <= bottom) { + for (let i = 0; i < chart.scales.x.ticks.length; i++) { + if (x >= left + (right * i) && x <= left + (right * (i + 1))) { + console.log('x label', i); + const regexPattern = /^(\S+): (.+)$/; + const matchResult = all[i].match(regexPattern); + if (matchResult) { + // Extracted parts + const dateSampled = matchResult[1]; + const sample = matchResult[2]; + + // Output the results + console.log("Date Sampled: ", dateSampled); + console.log("Sample:", sample); + createHighlights(meas, yLogLin, dateSampled, sample, null); + } else { + console.log("String format doesn't match the expected pattern."); + }; + } + } + } + + } + ctx.addEventListener('click', (e) => { + clickableScales(chartInstance[instanceNo], ctx, e); + chartInstance[instanceNo].resize(); + //chartInstance[instanceNo].updtae(); + }); + const xLabels = document.querySelectorAll('#chart' + instanceNo + '.chartjs-axis-x .chartjs-axis-label'); + xLabels.forEach((label, index) => { + label.addEventListener('click', () => { + console.log('about to toggle'); + toggleHighlightMapLocation(index); + }); + }); + createResetZoomButton(chartInstance[instanceNo], instanceNo); +} + + +function displayChemicalChart(meas, sheetName, instanceNo, yLogLin, unitTitle) { +createCanvas(instanceNo); +const convas = document.getElementById("chart" + instanceNo); +convas.style.display = "block"; +instanceType[instanceNo] = 'sample'; +instanceSheet[instanceNo] = sheetName; +const allChemicals = Object.keys(meas); +const allSamples = Object.keys(meas[allChemicals[0]]); // Assuming all samples have the same chemicals +const datasets = allSamples.map((sample, index) => { + const data = allChemicals.map(chemical => meas[chemical][sample]); // Using the first concentration value for simplicity + return { + label: sample, + data: data, + borderWidth: 1, + yAxisID: 'y', + }; +}); +displayAnyChart(meas, allChemicals,datasets,instanceNo,sheetName,unitTitle,yLogLin); +chartInstance[instanceNo].options.plugins.annotation.annotations = {}; +let allal = actionLevels[sheetName]; + + +if(allal) { + allChemicals.forEach (chemical => { + let al = allal[chemical] ? allal[chemical].slice() : null; + alMax = 0; + al2 = false; + if(al) { + item = allChemicals.indexOf(chemical); + for (i = 0; i < 2; i++) { + if (al[i] > 0.0) { +// borderColor = actionLevelColors[i]; + // Get the units right between action levels and sample measurements + // Action levels are all in mg/kg but PAHs in ug/kg + if (sheetName === 'PAH data') { + al[i] = al[i] * 1000; + } + if (i === 1) { + al2 = true; + } + chartLine(instanceNo,chemical + i,item-0.5,item+0.5,al[i],al[i],actionLevelColors[i],actionLevelDashes[i]); + } + } + if (al[1] > alMax) { + alMax = al[1]; + } + } + }); + maxConc = findMaxConcentration(meas); +//console.log('maxConc ',maxConc); +//console.log(meas); + if (maxConc > alMax) { + alMax = maxConc; + } + alX = allChemicals.length * 0.03; + chartLabel(instanceNo,alX,0.8*alMax,actionLevelColors[0],'Action Level 1 '); + chartLine(instanceNo,'Legend - Action Level 1',alX*1.4,alX*2.5,0.8*alMax,0.8*alMax,actionLevelColors[0],actionLevelDashes[0]); + if (al2) { + chartLabel(instanceNo,alX,0.9*alMax,actionLevelColors[1],'Action Level 2 '); + chartLine(instanceNo,'Legend - Action Level 2',alX*1.4,alX*2.5,0.9*alMax,0.9*alMax,actionLevelColors[1],actionLevelDashes[1]); + } +} + // Update the chart + chartInstance[instanceNo].update(); +} + +// chartInstance[3].resetZoom(); +// + +// Function to find the maximum concentration +function findMaxConcentration(data) { +let maxConcentration = -Infinity; + +for (const chemical in data) { +for (const sample in data[chemical]) { + const concentration = data[chemical][sample]; + if (concentration > maxConcentration) { + maxConcentration = concentration; + } +} +} + +return maxConcentration; +} + +function chartLine(instanceNo,name,xMin,xMax,yMin,yMax,borderColor,borderDash) { +// chartInstance[instanceNo].options.plugins.annotation.annotations.push({ + chartInstance[instanceNo].options.plugins.annotation.annotations[('line-' + instanceNo + '-' + name)] = { + type: 'line', + yMin: yMin, + yMax: yMax, + xMin: xMin, + xMax: xMax, + borderColor: borderColor, + borderDash: borderDash, + borderWidth: 2, + }; +} + +function chartLabel(instanceNo,xValue,yValue,borderColor,label) { +// chartInstance[instanceNo].options.plugins.annotation.annotations.push({ + chartInstance[instanceNo].options.plugins.annotation.annotations[('label-' + instanceNo + '-'+label)] = { + type: 'label', + enabled: true, + xValue: xValue, + yValue: yValue, + color: borderColor, + backgroundColor: 'rgba(200,200,200)', + content: [label], + font: { + size: 10 + } + }; +} + +function displayGorhamTest(sums, sheetName, instanceNo, yLogLin, unitTitle) { + createCanvas(instanceNo); + const convas = document.getElementById("chart" + instanceNo); + convas.style.display = "block"; + instanceType[instanceNo] = 'gorham'; +instanceSheet[instanceNo] = sheetName; + const lmw = ['Acenaphthene', 'Acenaphthylene', 'Anthracene', 'Fluorene', 'C1-Naphthalenes', 'Naphthalene', 'Phenanthrene']; + const hmw = ['Benz[a]anthracene', 'Benzo[a]pyrene', 'Chrysene', 'Dibenz[a,h]anthracene', 'Fluoranthene', 'Pyrene']; + const LMW = { + ERL: 552, + ERM: 3160 + }; + const HMW = { + ERL: 1700, + ERM: 9600 + }; + + const samples = Object.keys(sums); + const lmwSumData = samples.map(sample => sums[sample].lmwSum); + const hmwSumData = samples.map(sample => sums[sample].hmwSum); + datasets = [ + { + label: 'LMW Sums', + backgroundColor: 'rgba(54, 162, 235, 0.5)', + borderColor: 'rgba(54, 162, 235, 1)', + borderWidth: 1, + data: lmwSumData, + yAxisID: 'y', + }, + { + label: 'HMW Sums', + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132, 1)', + borderWidth: 1, + data: hmwSumData, + yAxisID: 'y', + }, + ]; + +displayAnyChart(sums, samples,datasets,instanceNo,sheetName + ': Gorham Test Protocol',unitTitle,yLogLin); + + chartInstance[instanceNo].options.plugins.annotation.annotations = {}; + chartLine(instanceNo,'LMW.ERL',0,samples.length,LMW.ERL,LMW.ERL,'rgba(0, 0, 255, 0.5)',[3,3]); + chartLine(instanceNo,'LMW.ERM',0,samples.length,LMW.ERM,LMW.ERM,'rgba(0, 0, 255, 0.5)',[5,5]); + chartLine(instanceNo,'HMW.ERL',0,samples.length,HMW.ERL,HMW.ERL,'rgba(255, 0, 0, 0.5)',[3,3]); + chartLine(instanceNo,'HMW.ERM',0,samples.length,HMW.ERM,HMW.ERM,'rgba(255, 0, 0, 0.5)',[5,5]); + gorX = samples.length * 0.05; + gorMax = HMW.ERM; + for (const sample in sums) { + if (sums[sample].lmwSum > gorMax) { + gorMax = sums[sample].lmwSum; + } + if (sums[sample].hmwSum > gorMax) { + gorMax = sums[sample].hmwSum; + } + } + chartLabel(instanceNo,gorX,0.75*gorMax,'rgba(0, 0, 255, 0.5)','LMW ERL '); + chartLine(instanceNo,'Legend - LMW.ERL',gorX*1.2,gorX*2.2,0.75*gorMax,0.75*gorMax,'rgba(0, 0, 255, 0.5)',actionLevelDashes[0]); + chartLabel(instanceNo,gorX,0.8*gorMax,'rgba(0, 0, 255, 0.5)','LMW ERM '); + chartLine(instanceNo,'Legend - LMW.ERM',gorX*1.2,gorX*2.2,0.8*gorMax,0.8*gorMax,'rgba(0, 0, 255, 0.5)',actionLevelDashes[1]); + chartLabel(instanceNo,gorX,0.9*gorMax,'rgba(255, 0, 0, 0.5)','HMW ERL '); + chartLine(instanceNo,'Legend - HMW.ERL',gorX*1.2,gorX*2.2,0.9*gorMax,0.9*gorMax,'rgba(255, 0, 0, 0.5)',actionLevelDashes[0]); + chartLabel(instanceNo,gorX,0.95*gorMax,'rgba(255, 0, 0, 0.5)','HMW ERM '); + chartLine(instanceNo,'Legend - HMW.ERM',gorX*1.2,gorX*2.2,0.95*gorMax,0.95*gorMax,'rgba(255, 0, 0, 0.5)',actionLevelDashes[1]); + // Update the chart + chartInstance[instanceNo].update(); +} + +function displayCongener(sums, sheetName, instanceNo, yLogLin, unitTitle) { + createCanvas(instanceNo); + const convas = document.getElementById("chart" + instanceNo); + convas.style.display = "block"; + instanceType[instanceNo] = 'congener'; +instanceSheet[instanceNo] = sheetName; + const samples = Object.keys(sums); + const ICES7SumData = samples.map(sample => sums[sample].ICES7); + const allSumData = samples.map(sample => sums[sample].All); + const datasets = [ + { + label: 'ICES7', + backgroundColor: 'rgba(54, 162, 235, 0.5)', + borderColor: 'rgba(54, 162, 235, 1)', + borderWidth: 1, + data: ICES7SumData, + yAxisID: 'y', + }, + { + label: 'All', + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132, 1)', + borderWidth: 1, + data: allSumData, + yAxisID: 'y', + }, + ]; +displayAnyChart(sums, samples,datasets,instanceNo,sheetName + ': Congener Sums',unitTitle,yLogLin); + chartInstance[instanceNo].options.plugins.annotation.annotations = {}; + chartLine(instanceNo,'ICES7 Action Level 1',0,samples.length,0.01,0.01,'rgba(0, 0, 255, 0.5)',[3,3]); + chartLine(instanceNo,'All Action Level 1',0,samples.length,0.02,0.02,'rgba(255, 0, 0, 0.5)',[3,3]); + chartLine(instanceNo,'All Action Level 2',0,samples.length,0.2,0.2,'rgba(255, 0, 0, 0.5)',[5,5]); + gorX = samples.length * 0.1; + gorMax = 0.2; + for (const sample in sums) { + if (sums[sample].ICES7 > gorMax) { + gorMax = sums[sample].ICES7; + } + if (sums[sample].All > gorMax) { + gorMax = sums[sample].All; + } + } + chartLabel(instanceNo,gorX,0.75*gorMax,'rgba(0, 0, 255, 0.5)','ICES7 Action Level 1 '); + chartLine(instanceNo,'Legend - ICES7 AL1',gorX*1.2,gorX*2.2,0.75*gorMax,0.75*gorMax,'rgba(0, 0, 255, 0.5)',actionLevelDashes[0]); + chartLabel(instanceNo,gorX,0.9*gorMax,'rgba(255, 0, 0, 0.5)','All Action Level 1 '); + chartLine(instanceNo,'Legend - All AL1',gorX*1.2,gorX*2.2,0.9*gorMax,0.9*gorMax,'rgba(255, 0, 0, 0.5)',actionLevelDashes[0]); + chartLabel(instanceNo,gorX,0.95*gorMax,'rgba(255, 0, 0, 0.5)','All Action Level 2 '); + chartLine(instanceNo,'Legend - All AL2',gorX*1.2,gorX*2.2,0.95*gorMax,0.95*gorMax,'rgba(255, 0, 0, 0.5)',actionLevelDashes[1]); + // Update the chart + chartInstance[instanceNo].update(); +} + +function displayTotalHC(sums, sheetName, instanceNo, yLogLin, unitTitle) { + createCanvas(instanceNo); + const convas = document.getElementById("chart" + instanceNo); + convas.style.display = "block"; + instanceType[instanceNo] = 'totalHC'; +instanceSheet[instanceNo] = sheetName; + const samples = Object.keys(sums); + const totalHC = samples.map(sample => sums[sample].totalHC); + const fractionPAH = samples.map(sample => sums[sample].fractionPAH); + const datasets = [ + { + label: 'totalHC', + backgroundColor: 'rgba(54, 162, 235, 0.5)', + borderColor: 'rgba(54, 162, 235, 1)', + borderWidth: 1, + data: totalHC, + yAxisID: 'y', + }, + { + label: 'fractionPAH', + backgroundColor: 'rgba(255, 99, 132, 0.5)', + borderColor: 'rgba(255, 99, 132, 1)', + borderWidth: 1, + data: fractionPAH, + yAxisID: 'y1', + }, + ]; +displayAnyChart(sums, samples,datasets,instanceNo,sheetName + ': Total hydrocarbon & Total PAH/THC',unitTitle,yLogLin); +y1Title = 'Fraction PAH of THC'; +/* chartInstance[instanceNo].options.scales.push({ + y1: { beginAtZero: true, + type: yLogLin, + title: { + display: true, + text: y1Title, + position: 'right', + } + } + });*/ + +/* chartInstance[instanceNo].scales[('y1')] = { + y1: { beginAtZero: true, + type: yLogLin, + position: 'right', + title: { + display: true, + text: 'Something', + position: 'right', + } + }, + };*/ +chartInstance[instanceNo].options.plugins.legend.display = true; + + /*chartInstance[instanceNo].options.plugins.annotation.annotations = {}; + chartLine(instanceNo,'ICES7 Action Level 1',0,samples.length,0.01,0.01,'rgba(0, 0, 255, 0.5)',[3,3]); + chartLine(instanceNo,'All Action Level 1',0,samples.length,0.02,0.02,'rgba(255, 0, 0, 0.5)',[3,3]); + chartLine(instanceNo,'All Action Level 2',0,samples.length,0.2,0.2,'rgba(255, 0, 0, 0.5)',[5,5]); + gorX = samples.length * 0.1; + gorMax = 0.2; + for (const sample in sums) { + if (sums[sample].totalHC > gorMax) { + gorMax = sums[sample].ICES7; + } + if (sums[sample].All > gorMax) { + gorMax = sums[sample].All; + } + } + chartLabel(instanceNo,gorX,0.75*gorMax,'rgba(0, 0, 255, 0.5)','ICES7 Action Level 1 '); + chartLine(instanceNo,'Legend - ICES7 AL1',gorX*1.2,gorX*2.2,0.75*gorMax,0.75*gorMax,'rgba(0, 0, 255, 0.5)',actionLevelDashes[0]); + chartLabel(instanceNo,gorX,0.9*gorMax,'rgba(255, 0, 0, 0.5)','All Action Level 1 '); + chartLine(instanceNo,'Legend - All AL1',gorX*1.2,gorX*2.2,0.9*gorMax,0.9*gorMax,'rgba(255, 0, 0, 0.5)',actionLevelDashes[0]); + chartLabel(instanceNo,gorX,0.95*gorMax,'rgba(255, 0, 0, 0.5)','All Action Level 2 '); + chartLine(instanceNo,'Legend - All AL2',gorX*1.2,gorX*2.2,0.95*gorMax,0.95*gorMax,'rgba(255, 0, 0, 0.5)',actionLevelDashes[1]);*/ + // Update the chart + chartInstance[instanceNo].update(); +} + + +function findSamplesInSameLocation(clickedMapSample) { + for (ds in selectedSampleInfo) { + for (s in selectedSampleInfo[ds].position) { + if (clickedMapSample === s) { + lat = selectedSampleInfo[ds].position[s]['Position latitude']; + lon = selectedSampleInfo[ds].position[s]['Position longitude']; + } + } + } + clickedMapSamples = []; + for (ds in selectedSampleInfo) { + for (s in selectedSampleInfo[ds].position) { + const testPos = selectedSampleInfo[ds].position[s]; + if (testPos['Position latitude'] === lat && testPos['Position longitude'] === lon) { + clickedMapSamples.push(ds + ': ' + s); + } + } + } + return clickedMapSamples +} + +function createHighlights(meas, linLog, dateSampled, hoveredSample, isMarked) { + const datesSampled = Object.keys(selectedSampleInfo); + datesSampled.sort(); + samples = []; + datesSampled.forEach(dateSampled => { + // for (const dateSampled in selectedSampleInfo) { + const ok = Object.keys(selectedSampleInfo[dateSampled].position); + // noSamples += Object.keys(selectedSampleInfo[dateSampled].position).length; + noSamples += ok.length; + for (const sample in selectedSampleInfo[dateSampled].position) { + samples.push(dateSampled + ': ' + sample); + } + // } + }); + console.log(hoveredSample); + if (!dateSampled) { + clickedSamples = findSamplesInSameLocation(hoveredSample); + console.log('Not dateSampled'); + } else { + clickedSamples = []; + clickedSamples[0] = dateSampled + ': ' + hoveredSample; + console.log('dateSampled'); + } + console.log(clickedSamples); + const allChemicals = Object.keys(meas); + let clickedIndexes = []; + console.log('samples', samples); + console.log('meas[allChemicals[0]]', Object.keys(meas[allChemicals[0]])); + clickedSamples.forEach(clickedSample => { + index = -1; + samples.forEach(sample => { + index += 1; + if (sample === clickedSample) { + clickedIndexes.push(index); + } + }); + }); + console.log(clickedIndexes); + clickedIndexes.forEach(item => { + if (isMarked === null) { + console.log('doing the null bit'); + if (highlighted[item]) { + highlighted[item] = false; + } else { + highlighted[item] = true; + } + } else { + highlighted[item] = !isMarked; + } + }); + console.log(highlighted); + clickedIndexes.forEach(item => { + console.log(item); + for (let i = 1; i < lastInstanceNo + 1; i++) { + if (instanceType[i] === 'gorham' || instanceType[i] === 'chemical' || instanceType[i] === 'congener' || instanceType[i] === 'totalHC') { + if (highlighted[item]) { + displayChartHighlight(meas, linLog, i, dateSampled, item); + } else { + removeChartHighlight(meas, linLog, i, dateSampled, item); + } + } + } + }); +} + +// Function to display the chart based on the clicked sample +function displayChartHighlight(meas, yLogLin, instanceNo, dateSampled, item) { + // Draw a rectangle around the clicked data +console.log('about to highlight',instanceNo,item); + chartInstance[instanceNo].options.plugins.annotation.annotations[('tempBox-' + instanceNo + '-'+item)] = { + type: 'box', + xScaleID: 'x', + yScaleID: 'y', + xMin: item - 0.5, // Adjust based on your data and preferences + xMax: item + 0.5, + borderWidth: 2, + borderColor: 'red', + backgroundColor: 'rgba(255, 0, 0, 0.1)', + id: `tempBox-$(instanceNo)-$(item)`, + }; + // Update the chart + chartInstance[instanceNo].update(); +} + +function removeChartHighlight(meas, yLogLin, instanceNo, dateSampled, item) { +console.log('about to remove highlight',instanceNo,item); + delete chartInstance[instanceNo].options.plugins.annotation.annotations['tempBox-' + instanceNo + '-'+item]; + // Update the chart + chartInstance[instanceNo].update(); +} + + + diff --git a/sdeMaps.js b/sdeMaps.js new file mode 100644 index 0000000..840652a --- /dev/null +++ b/sdeMaps.js @@ -0,0 +1,226 @@ +function sampleMap(meas, linLog) { + // Check if there's an existing map and remove it + if (map) { + map.remove(); + } + // SampleInfo data structure + // Initialize the map + map = L.map('map').setView([54.596, -1.177], 13); // Set the initial center and zoom level + + // Add OpenStreetMap tile layer + L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: '© OpenStreetMap contributors' + }).addTo(map); + + latSum = 0; + lonSum = 0; + noLocations = 0; + minLat = null; + maxLat = null; + minLon = null; + maxLon = null; + let hoveredSample = null; + + var greenIcon = L.icon({ + iconUrl: 'blue-marker-icon.png', // Replace with the path to your marker icon + shadowUrl: 'marker-shadow.png', + + iconSize: [38, 95], // size of the icon + shadowSize: [50, 64], // size of the shadow + iconAnchor: [22, 94], // point of the icon which will correspond to marker's location + shadowAnchor: [4, 62], // the same for the shadow + popupAnchor: [-3, -76] // point from which the popup should open relative to the iconAnchor + }); + + // Define a custom marker icon with a specific color + var CustomIcon = L.Icon.extend({ + options: { + shadowUrl: 'marker-shadow.png', + iconSize: [25, 41], // Replace with the size of your marker icon + iconAnchor: [12, 41], // Replace with the anchor point of your marker icon + popupAnchor: [1, -34], // Replace with the popup anchor point of your marker icon + } + }); + // Add markers for each sample + iconNo = 0; + + const datesSampled = Object.keys(selectedSampleInfo); + datesSampled.sort(); + datesSampled.forEach(dateSampled => { + currentIcon = new CustomIcon({ iconUrl: markerPngs[iconNo] }); + iconNo = (iconNo + 1) % 9; + noSamples = 0; + for (const dateSampled in selectedSampleInfo) { + noSamples += Object.keys(selectedSampleInfo[dateSampled].position).length; + } + console.log('noSamples', noSamples); + highlighted = Array(noSamples).fill(false); + for (const sample in selectedSampleInfo[dateSampled].position) { + if (selectedSampleInfo[dateSampled].position[sample]['Position latitude']) { + lat = selectedSampleInfo[dateSampled].position[sample]['Position latitude']; + lon = selectedSampleInfo[dateSampled].position[sample]['Position longitude']; + // Create a marker for each sample + if (lat !== undefined && lon !== undefined) { + lat = parseFloat(lat); + lon = parseFloat(lon); + if (maxLat === null) { + minLat = lat; + maxLat = lat; + minLon = lon; + maxLon = lon; + } else { + if (lat > maxLat) { + maxLat = lat; + } else if (lat < minLat) { + minLat = lat; + } + if (lon > maxLon) { + maxLon = lon; + } else if (lon < minLon) { + minLon = lon; + } + } + // Create a marker for each sample + // const marker = L.marker([lat, lon]).addTo(map).bindPopup(`${sample}
Latitude: ${lat}
Longitude: ${lon}`); + const marker = L.marker([lat, lon], { icon: currentIcon }).addTo(map).bindPopup(`${sample}
Latitude: ${lat}
Longitude: ${lon}`); + /* const marker = L.circleMarker([lat, lon], + {radius: 4, color: 'white', fillColor: 'red', fillOpacity: 1} + ).addTo(map).bindPopup(`${sample}
Latitude: ${lat}
Longitude: ${lon}`); + marker.bindTooltip(sample, { permanent: false, direction: 'top' });*/ + marker.isMarked = false; + + // Add a click event listener to the marker + marker.on('click', function () { + hoveredSample = sample; + createHighlights(meas, linLog, null, hoveredSample, marker.isMarked); + if (!marker.isMarked) { + marker.isMarked = true; + } else { + marker.isMarked = false; + } + // Update the chart - in routintes + //console.log('update ',sample,i); + // chartInstance[i].update(); + }); + + noLocations += 1; + latSum += parseFloat(lat); + lonSum += parseFloat(lon); + }; + } + + } + /* iconNo += 1; + if (iconNo > 8) { + iconNo = 0; + }*/ + console.log(iconNo, dateSampled); + }); + + if (noLocations > 0) { + const centreLat = latSum / noLocations; + const centreLon = lonSum / noLocations; + var bounds = L.latLngBounds([minLat, minLon], [maxLat, maxLon]); + map.fitBounds(bounds); + //console.log('lat,lon ',minLat,minLon, maxLat,maxLon); + /* if (centreLat !== undefined && centreLon !== undefined) { + map.setView(new L.LatLng(centreLat, centreLon), 13); + }*/ + // } + } + +} + +function randomColor() { + return '#' + Math.floor(Math.random() * 16777215).toString(16); +} + +function exportChart() { + const now = new Date(); + const formattedDate = now + .toISOString() + .slice(2, 16) + .replace(/[-T:]/g, ''); // Format: yymmddhhmm + + for (i = 1; i 360) { +// Use proj4js library to convert British National Grid to latitude and longitude +proj4.defs("EPSG:27700", "+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +towgs84=446.448,-125.157,542.060,0.1502,0.2470,0.8421,-20.4894 +units=m +no_defs"); +const point = proj4("EPSG:27700", "EPSG:4326", [parseInt(latitude, 10), parseInt(longitude, 10)]); + +return { latitude: point[1], longitude: point[0] }; +} else { +return { latitude: parseCoordinate(latitude), longitude: parseCoordinate(longitude) }; +} + +// If the input doesn't match any recognized format, return null or handle accordingly +return null; +} + diff --git a/sdeSelections.js b/sdeSelections.js new file mode 100644 index 0000000..642f1cd --- /dev/null +++ b/sdeSelections.js @@ -0,0 +1,442 @@ +function openChemicalSelection(sampleMeasurements) { + const chemicalModal = document.getElementById('chemicalModal'); + chemicalModal.style.display = 'block'; + + const sampleCheckboxes = document.getElementById('chemicalCheckboxes'); + chemicalCheckboxes.innerHTML = ''; + + const datesSampled = Object.keys(selectedSampleMeasurements); + + for (chemicalType in selectedSampleMeasurements[datesSampled[0]]) { + if (chemicalType !== 'Physical Data') { + const chemicals = Object.keys(selectedSampleMeasurements[datesSampled[0]][chemicalType].chemicals); + + const checkboxContainer = document.createElement('div'); + checkboxContainer.className = 'checkbox-container'; + + chemicals.forEach(chemical => { + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.id = `chemical`; + checkbox.name = 'chemical'; + checkbox.value = chemical; + checkbox.checked = true; // Initially all chemicals are checked + + const label = document.createElement('label'); + label.htmlFor = `chemical_${chemical}`; + label.appendChild(document.createTextNode(chemical)); + + checkboxContainer.appendChild(checkbox); + checkboxContainer.appendChild(label); + checkboxContainer.appendChild(document.createElement('br')); + }); + + chemicalCheckboxes.appendChild(checkboxContainer); + } + } + } + + function flipChemicalSelections(selection) { + const checkboxes = document.querySelectorAll('#chemicalCheckboxes input[type="checkbox"]'); + checkboxes.forEach(checkbox => { + if (selection) { + checkbox.checked = true; + } else { + checkbox.checked = false; + }; + }); + } + + function applyChemicalFilter() { + const containsText = document.getElementById('containsTextChemical').value.toLowerCase(); + const checkboxes = document.querySelectorAll('#chemicalCheckboxes input[type="checkbox"]'); + checkboxes.forEach(checkbox => { + if (containsText.length > 0) { + if (checkbox.nextSibling.textContent.toLowerCase().includes(containsText)) { + checkbox.checked = true; + } else { + checkbox.checked = false; + } + } + }); + } + + function closeChemicalSelection() { + selectChemicals(); + const chemicalModal = document.getElementById('chemicalModal'); + chemicalModal.style.display = 'none'; + } + + function selectChemicals() { + const checkboxes = document.querySelectorAll('input[name="chemical"]:checked'); + const selectedChemicals = Array.from(checkboxes) + .filter(checkbox => checkbox.checked) + .map(checkbox => checkbox.value); + selectedSampleMeasurements = getSelectedChemicalSampleMeasurements(selectedChemicals); + selectedSampleInfo = getSelectedChemicalSampleInfo(selectedChemicals); + updateChart(); + } + + function getSelectedChemicalSampleMeasurements(selectedChemicals) { + selectedMeas = {}; + for (dateSampled in sampleMeasurements) { + for (const chemicalType in selectedSampleMeasurements[dateSampled]) { + for (const chemical in selectedSampleMeasurements[dateSampled][chemicalType].chemicals) { + if (selectedChemicals.includes(chemical)) { + // Put here as if no chemicals selected then don't need chemical type + if (!selectedMeas[dateSampled]) { + selectedMeas[dateSampled] = {}; + } + if (!selectedMeas[dateSampled][chemicalType]) { + selectedMeas[dateSampled][chemicalType] = {}; + selectedMeas[dateSampled][chemicalType].chemicals = {}; + if (chemicalType == 'PAH data') { + // Just copy all common data even if only 1 PAH is selected + selectedMeas[dateSampled][chemicalType].gorhamTest = selectedSampleMeasurements[dateSampled][chemicalType].gorhamTest; + selectedMeas[dateSampled][chemicalType].total = selectedSampleMeasurements[dateSampled][chemicalType].total; + selectedMeas[dateSampled][chemicalType].totalHC = selectedSampleMeasurements[dateSampled][chemicalType].totalHC; + selectedMeas[dateSampled][chemicalType].totalHCUnit = selectedSampleMeasurements[dateSampled][chemicalType].totalHCUnit; + } + } + selectedMeas[dateSampled][chemicalType].chemicals[chemical] = selectedSampleMeasurements[dateSampled][chemicalType].chemicals[chemical]; + } + } + } + } + return selectedMeas; + } + + function getSelectedChemicalSampleInfo(selectedChemicals) { + selectedSamps = {}; + for (const dateSampled in selectedSampleMeasurements) { + for (const chemicalType in selectedSampleMeasurements[dateSampled]) { + selectedSamps[dateSampled] = {}; + selectedSamps[dateSampled]['Date sampled'] = selectedSampleInfo[dateSampled]['Date sampled']; + selectedSamps[dateSampled].fileURL = selectedSampleInfo[dateSampled].fileURL; + selectedSamps[dateSampled].Applicant = selectedSampleInfo[dateSampled].Applicant; + selectedSamps[dateSampled]['Application number'] = selectedSampleInfo[dateSampled]['Application number']; + selectedSamps[dateSampled]['Application title'] = selectedSampleInfo[dateSampled]['Application title']; + selectedSamps[dateSampled].position = {}; + for (const chemical in selectedSampleMeasurements[dateSampled][chemicalType].chemicals) { + for (const sample in selectedSampleMeasurements[dateSampled][chemicalType].chemicals[chemical].samples){ + selectedSamps[dateSampled].position[sample] = selectedSampleInfo[dateSampled].position[sample]; + } + } + } + } + return selectedSamps; + } + + function openSampleSelection(sampleMeasurements) { + const sampleModal = document.getElementById('sampleModal'); + sampleModal.style.display = 'block'; + + const sampleCheckboxes = document.getElementById('sampleCheckboxes'); + sampleCheckboxes.innerHTML = ''; + + const datesSampled = Object.keys(sampleInfo); + + + datesSampled.sort(); + datesSampled.forEach (dateSampled => { + + // for (dateSampled in sampleInfo) { + const samples = Object.keys(sampleInfo[dateSampled].position); + const checkboxContainer = document.createElement('div'); + checkboxContainer.className = 'checkbox-container'; + + samples.forEach(sample => { + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.id = `sample_${dateSampled + ': ' + sample}`; + checkbox.name = 'sample'; + checkbox.value = dateSampled + ': ' + sample; + checkbox.checked = true; // Initially all samples are checked + + const label = document.createElement('label'); + label.htmlFor = `sample_${sample}`; + label.appendChild(document.createTextNode(dateSampled + ': ' + sample)); + + checkboxContainer.appendChild(checkbox); + checkboxContainer.appendChild(label); + checkboxContainer.appendChild(document.createElement('br')); + }); + + sampleCheckboxes.appendChild(checkboxContainer); + }); + } + + function flipSampleSelections(selection) { + const checkboxes = document.querySelectorAll('#sampleCheckboxes input[type="checkbox"]'); + checkboxes.forEach(checkbox => { + if (selection) { + checkbox.checked = true; + } else { + checkbox.checked = false; + }; + }); + } + + function applySampleFilter() { + const containsText = document.getElementById('containsText').value.toLowerCase(); + const minDepth = parseFloat(document.getElementById('minDepth').value); + const maxDepth = parseFloat(document.getElementById('maxDepth').value); + centreLat = parseFloat(document.getElementById('centreLat').value); + centreLon = parseFloat(document.getElementById('centreLon').value); + const centreDist = parseFloat(document.getElementById('centreDist').value); + const checkboxes = document.querySelectorAll('#sampleCheckboxes input[type="checkbox"]'); + const checkboxesIn = document.querySelectorAll('input[name="sample"]:checked'); + checkboxes.forEach(checkbox => { + if (containsText.length > 0) { + if (checkbox.nextSibling.textContent.toLowerCase().includes(containsText)) { + checkbox.checked = true; + } else { + checkbox.checked = false; + } + } + }); + if (!(isNaN(minDepth) || isNaN(maxDepth)) && (minDepth <= maxDepth)) { + checkboxes.forEach(checkbox => { + checkbox.checked = false; + }); + for (const dateSelected in sampleInfo) { + for (const sample in sampleInfo[dateSelected].position) { + const minSample = sampleInfo[dateSelected].position[sample]['Sampling depth (m)'].minDepth; + if (minDepth <= minSample) { + const maxSample = sampleInfo[dateSelected].position[sample]['Sampling depth (m)'].maxDepth; + if (maxDepth >= maxSample) { + const checkName = `sample_${dateSelected + ': ' + sample}`; + const checkbox = document.getElementById(checkName); + checkbox.checked = true; + } + } + } + } + } + // going to find samples close to a set of coordinates + if (!isNaN(centreDist)) { + // latitude and longitude not supplied + if ((isNaN(centreLat) || isNaN(centreLon))) { + // so assuming only one checkbox is ticked then find all samples near to that position + const selectedSamples = Array.from(checkboxesIn) + .filter(checkbox => checkbox.checked) + .map(checkbox => checkbox.value); + if (selectedSamples.length === 1) { + for (const dateSampled in sampleInfo) { + for (const sample in sampleInfo[dateSampled].position) { + if (selectedSamples.includes(dateSampled + ': ' + sample)) { + centreLat = sampleInfo[dateSampled].position[sample]['Position latitude']; + centreLon = sampleInfo[dateSampled].position[sample]['Position longitude']; + } + } + } + } else { + return + } + } + checkboxes.forEach(checkbox => { + checkbox.checked = false; + }); + for (const dateSelected in sampleInfo) { + for (const sample in sampleInfo[dateSelected].position) { + const sampleLat = sampleInfo[dateSelected].position[sample]['Position latitude']; + const sampleLon = sampleInfo[dateSelected].position[sample]['Position longitude']; + distance = 1000 * haversineDistance(sampleLat, sampleLon, centreLat, centreLon); + if (distance <= centreDist) { + const checkName = `sample_${dateSelected + ': ' + sample}`; + const checkbox = document.getElementById(checkName); + checkbox.checked = true; + } + } + } + } + } + + function haversineDistance(lat1, lon1, lat2, lon2) { + const R = 6371; // Radius of the Earth in kilometers + const dLat = toRadians(lat2 - lat1); + const dLon = toRadians(lon2 - lon1); + const a = + Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(toRadians(lat1)) * Math.cos(toRadians(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2); + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + const distance = R * c; // Distance in kilometers + return distance; + } + + function toRadians(degrees) { + return degrees * (Math.PI / 180); + } + + + function closeSampleSelection() { + selectSamples(); + const sampleModal = document.getElementById('sampleModal'); + sampleModal.style.display = 'none'; + } + + function selectSamples() { + const checkboxes = document.querySelectorAll('input[name="sample"]:checked'); + //console.log(checkboxes); + const selectedSamples = Array.from(checkboxes) + .filter(checkbox => checkbox.checked) + .map(checkbox => checkbox.value); + //console.log(selectedSamples); + selectedSampleMeasurements = getselectedSampleMeasurements(selectedSamples); + selectedSampleInfo = getSelectedSamples(selectedSamples); + updateChart(); + } + + + function getselectedSampleMeasurements(selectedSamples) { + selectedMeas = {}; + //console.log(selectedSamples); + for (dateSampled in sampleMeasurements) { + //console.log(dateSampled); + const chemicalTypes = Object.keys(sampleMeasurements[dateSampled]); + const chemicals = Object.keys(sampleMeasurements[dateSampled][chemicalTypes[0]].chemicals); + for (const chemicalType in sampleMeasurements[dateSampled]) { + if (chemicalType === 'Physical Data') { + for (const sample in sampleMeasurements[dateSampled][chemicalType].samples) { + if (selectedSamples.includes(dateSampled + ': ' + sample)) { + if (!selectedMeas[dateSampled]) { + selectedMeas[dateSampled] = {}; + selectedMeas[dateSampled][chemicalType] = {}; + selectedMeas[dateSampled][chemicalType].samples = {}; + selectedMeas[dateSampled][chemicalType]['Unit of measurement'] = sampleMeasurements[dateSampled][chemicalType]['Unit of measurement']; + selectedMeas[dateSampled][chemicalType].sizes = sampleMeasurements[dateSampled][chemicalType].sizes; + //console.log('1 psd selectedMeas ',dateSampled,chemicalType,selectedMeas); + } else { + if (!selectedMeas[dateSampled][chemicalType]) { + selectedMeas[dateSampled][chemicalType] = {}; + selectedMeas[dateSampled][chemicalType].samples = {}; + selectedMeas[dateSampled][chemicalType]['Unit of measurement'] = sampleMeasurements[dateSampled][chemicalType]['Unit of measurement']; + selectedMeas[dateSampled][chemicalType].sizes = sampleMeasurements[dateSampled][chemicalType].sizes; + //console.log('2 psd selectedMeas ',dateSampled,chemicalType,selectedMeas); + } + //console.log('3 psd selectedMeas ',dateSampled,chemicalType,sample,selectedMeas); + selectedMeas[dateSampled][chemicalType].samples[sample] = {}; + selectedMeas[dateSampled][chemicalType].samples[sample].psd = sampleMeasurements[dateSampled][chemicalType].samples[sample].psd; + } + } + } + + //console.log('Create ', dateSampled, chemicalType); + //console.log('should do Gorham Test here'); + + } else { + for (const chemical in sampleMeasurements[dateSampled][chemicalType].chemicals) { + for (const sample in sampleMeasurements[dateSampled][chemicalType].chemicals[chemical].samples) { + //console.log(sample); + if (selectedSamples.includes(dateSampled + ': ' + sample)) { + //console.log('Found one'); + if (!selectedMeas[dateSampled]) { + //console.log('Create ', dateSampled); + selectedMeas[dateSampled] = {}; + // for (const chemicalType in sampleMeasurements[dateSampled]) { + selectedMeas[dateSampled][chemicalType] = {}; + selectedMeas[dateSampled][chemicalType]['Unit of measurement'] = sampleMeasurements[dateSampled][chemicalType]['Unit of measurement']; + selectedMeas[dateSampled][chemicalType].chemicals = {}; + //console.log('Create ', dateSampled, chemicalType); + //console.log('should do Gorham Test here'); + if (chemicalType == 'PAH data') { + //console.log('Create ', dateSampled, chemicalType,'Gorham Test'); + selectedMeas[dateSampled][chemicalType].gorhamTest = {}; + selectedMeas[dateSampled][chemicalType].gorhamTest[sample] = sampleMeasurements[dateSampled][chemicalType].gorhamTest[sample]; + selectedMeas[dateSampled][chemicalType].total = {}; + selectedMeas[dateSampled][chemicalType].total[sample] = selectedSampleMeasurements[dateSampled][chemicalType].total[sample]; + selectedMeas[dateSampled][chemicalType].totalHC = {}; + selectedMeas[dateSampled][chemicalType].totalHCUnit = selectedSampleMeasurements[dateSampled][chemicalType].totalHCUnit; + selectedMeas[dateSampled][chemicalType].totalHC[sample] = selectedSampleMeasurements[dateSampled][chemicalType].totalHC[sample]; + } + if (chemicalType == 'PCB data') { + //console.log('Create ', dateSampled, chemicalType,'Gorham Test'); + selectedMeas[dateSampled][chemicalType].congenerTest = {}; + selectedMeas[dateSampled][chemicalType].congenerTest[sample] = sampleMeasurements[dateSampled][chemicalType].congenerTest[sample]; + } + for (const chemical in sampleMeasurements[dateSampled][chemicalType].chemicals) { + //console.log('Create ', dateSampled, chemicalType,chemical); + selectedMeas[dateSampled][chemicalType].chemicals[chemical] = {}; + selectedMeas[dateSampled][chemicalType].chemicals[chemical].samples = {}; + selectedMeas[dateSampled][chemicalType].chemicals[chemical].samples[sample] = sampleMeasurements[dateSampled][chemicalType].chemicals[chemical].samples[sample]; + } + // } + } else { + // for (const chemicalType in sampleMeasurements[dateSampled]) { + //console.log('ch ', dateSampled,sample,chemicalType); + if (!selectedMeas[dateSampled][chemicalType]) { + selectedMeas[dateSampled][chemicalType] = {}; + selectedMeas[dateSampled][chemicalType]['Unit of measurement'] = sampleMeasurements[dateSampled][chemicalType]['Unit of measurement']; + selectedMeas[dateSampled][chemicalType].chemicals = {}; + if (chemicalType === 'PAH data') { + //console.log('Create ', dateSampled, chemicalType,'Gorham Test'); + selectedMeas[dateSampled][chemicalType].gorhamTest = {}; + selectedMeas[dateSampled][chemicalType].gorhamTest[sample] = sampleMeasurements[dateSampled][chemicalType].gorhamTest[sample]; + } + if (chemicalType == 'PCB data') { + //console.log('Create ', dateSampled, chemicalType,'Gorham Test'); + selectedMeas[dateSampled][chemicalType].congenerTest = {}; + selectedMeas[dateSampled][chemicalType].congenerTest[sample] = sampleMeasurements[dateSampled][chemicalType].congenerTest[sample]; + } + for (const chemical in sampleMeasurements[dateSampled][chemicalType].chemicals) { + //console.log('Create ', dateSampled, chemicalType,chemical); + selectedMeas[dateSampled][chemicalType].chemicals[chemical] = {}; + selectedMeas[dateSampled][chemicalType].chemicals[chemical].samples = {}; + selectedMeas[dateSampled][chemicalType].chemicals[chemical].samples[sample] = sampleMeasurements[dateSampled][chemicalType].chemicals[chemical].samples[sample]; + } + } else { + if (chemicalType === 'PAH data') { + //console.log('Create ', dateSampled, chemicalType,'Gorham Test'); + selectedMeas[dateSampled][chemicalType].gorhamTest[sample] = sampleMeasurements[dateSampled][chemicalType].gorhamTest[sample]; + selectedMeas[dateSampled][chemicalType].total[sample] = selectedSampleMeasurements[dateSampled][chemicalType].total[sample]; + selectedMeas[dateSampled][chemicalType].totalHC[sample] = selectedSampleMeasurements[dateSampled][chemicalType].totalHC[sample]; + } + if (chemicalType == 'PCB data') { + //console.log('Create ', dateSampled, chemicalType,'Gorham Test'); + selectedMeas[dateSampled][chemicalType].congenerTest[sample] = sampleMeasurements[dateSampled][chemicalType].congenerTest[sample]; + } + for (const chemical in sampleMeasurements[dateSampled][chemicalType].chemicals) { + selectedMeas[dateSampled][chemicalType].chemicals[chemical].samples[sample] = sampleMeasurements[dateSampled][chemicalType].chemicals[chemical].samples[sample]; + } + } + } + } + } + } + } + } + } + // console.log(selectedMeas); // Output each cell value to console + return selectedMeas; + } + + function getSelectedSamples(selectedSamples) { + selectedSamps = {}; + for (const dateSampled in sampleInfo) { + for (const sample in sampleInfo[dateSampled].position) { + if (selectedSamples.includes(dateSampled + ': ' + sample)) { + // console.log('a point ' + dateSampled + ': ' + sample); + if (!selectedSamps[dateSampled]) { + selectedSamps[dateSampled] = {}; + selectedSamps[dateSampled]['Date sampled'] = sampleInfo[dateSampled]['Date sampled']; + selectedSamps[dateSampled].fileURL = sampleInfo[dateSampled].fileURL; + selectedSamps[dateSampled].Applicant = sampleInfo[dateSampled].Applicant; + selectedSamps[dateSampled]['Application number'] = sampleInfo[dateSampled]['Application number']; + selectedSamps[dateSampled]['Application title'] = sampleInfo[dateSampled]['Application title']; + } + if (!selectedSamps[dateSampled].position) { + selectedSamps[dateSampled].position = {}; + } + selectedSamps[dateSampled].position[sample] = sampleInfo[dateSampled].position[sample]; + } + } + } + // console.log(selectedSamps); // Output each cell value to console + return selectedSamps; + } + + function clearSelections() { + selectedSampleMeasurements = sampleMeasurements; + selectedSampleInfo = sampleInfo; + } +