{"version":3,"file":"npms-vis.umd.min.js","sources":["../jssrc/js/trendSummary.js","../jssrc/js/bsbi-examples.js","../jssrc/js/npms-trends1.js","../jssrc/index.js","../jssrc/js/main.js"],"sourcesContent":["import * as d3 from 'd3'\n\nconst $ = jQuery // eslint-disable-line no-undef\n\nexport function updateTrendSummary(id, d) {\n\n if (d) {\n $(`#${id}-swatches`).css('display', '') //.show()\n $(`#${id}-no-trend`).css('display', 'none') //.hide()\n setColour(`${id}_decline_strong`, d.declineStrong)\n setColour(`${id}_decline_mod`, d.declineMod)\n setColour(`${id}_stable`, d.stable)\n setColour(`${id}_increase_mod`, d.increaseMod)\n setColour(`${id}_increase_strong`, d.increaseStrong)\n } else {\n $(`#${id}-swatches`).css('display', 'none') //.hide()\n $(`#${id}-no-trend`).css('display', '') //.show()\n }\n\n function setColour(id, val) {\n\n d3.select(`#${id}`).attr('fill', `rgb(${255 - 255 * Number(val)/100},255,255)`)\n const grey = 220 - Math.floor((val/100) * 220)\n d3.select(`#${id}-path`).attr('fill', `rgb(${grey},${grey},${grey})`)\n\n let title = d3.select(`#${id}`).select('title').attr('data-title')\n d3.select(`#${id}`).select('title').text(`${title}: ${val}%`)\n d3.select(`#${id}-path`).select('title').text(`${title}: ${val}%`)\n }\n}\n\nexport function trendSummary(id, font, fontSize) {\n\n const svgArrow = `M 2250 7256 l 0 -2813 l -61 -7 c -34 -3 -526 -6 -1093 -6 l -1031 -1 l 66 -62 c 36 -34 756 -714 1600 -1512 c 844 -797 1820 -1719 2169 -2049 c 349 -329 667 -630 705 -668 l 70 -68 l 230 217 c 1454 1373 3719 3512 4012 3790 l 373 353 l -1090 0 l -1090 0 l 0 2820 l 0 2820 l -2430 0 l -2430 0 l 0 -2814 z`\n const svgSquare = `M 5 3968 c -3 -7 -4 -897 -3 -1978 l 3 -1965 l 2080 0 l 2080 0 l 0 1975 l 0 1975 l -2078 3 c -1657 2 -2079 0 -2082 -10 z`\n const ss = 25\n //const sr = 22\n\n const swatches = [\n {\n svg: svgArrow,\n scale: 0.6,\n rot: 180,\n text: `Strong decline`,\n id: `${id}_decline_strong`,\n da: [ss * 4]\n },\n {\n svg: svgArrow,\n scale: 0.4,\n rot: 180,\n text: `Moderate decline`,\n id: `${id}_decline_mod`,\n da: [ss*3, ss]\n },\n {\n svg: svgSquare,\n scale: 0.2,\n rot: 45,\n text: 'Stable',\n id: `${id}_stable`,\n da: [ss*3, ss]\n },\n {\n svg: svgArrow,\n scale: 0.4,\n rot: 0,\n text: `Moderate increase`,\n id: `${id}_increase_mod`,\n da: [ss*3, ss]\n },\n {\n svg: svgArrow,\n scale: 0.6,\n rot: 0,\n text: `Strong increase`,\n id: `${id}_increase_strong`,\n da: [ss*3, ss]\n }\n ]\n\n // Graphic\n const divParent = d3.select(`#${id}`)\n const svg = divParent.append('svg')\n .attr('width', ss * 5 + 2)\n .attr('height', ss + 2)\n .style('overflow', 'visible')\n .style('vertical-align', 'bottom')\n\n const gMain = svg.append('g')\n gMain.attr('transform', 'translate(1 1)')\n // Don't set display of gSwatches to 'none' here otherwise Firefox doesn't calculate bbox\n const gSwatches = gMain.append('g').attr('id', `${id}-swatches`) //.style('display', 'none')\n const tNoTrend = gMain.append('text').attr('id', `${id}-no-trend`).text('No trend').style('display', 'none')\n .style('display', 'none')\n .attr('text-anchor', 'middle')\n .attr('x', ss * 5 / 2)\n .attr('y', ss/2)\n .attr('dominant-baseline', 'mathematical')\n\n if (font) {\n tNoTrend.style('font-family', font)\n tNoTrend.style('font-size', fontSize)\n }\n\n gMain.append('rect')\n .attr('width', ss * 5)\n .attr('height', ss)\n .attr('stroke','grey')\n .attr('fill','none')\n .style('vertical-align', 'bottom')\n\n // Swatches\n swatches.forEach((s,i) => {\n\n const indicator = gSwatches.append('rect')\n .attr('id', s.id)\n .attr('width', ss)\n .attr('height', ss)\n .attr('x', i * ss)\n .attr('fill', 'white')\n\n indicator.append('title').attr('data-title', s.text).text(s.text)\n\n if (s.svg) {\n const path = gSwatches.append('path').attr('d', s.svg).style('visibility', 'hidden')\n const svgbbox = path.node().getBBox()\n path.remove()\n\n // Note the order of the transformations is right to left\n // in the translate clause\n const iScale = (ss / svgbbox.width) * s.scale\n const xAdj = i * ss + (ss - svgbbox.width * iScale)/2\n const yAdj = (ss - svgbbox.height * iScale)/2\n const xRot = ss/2 + i*ss\n const yRot = ss/2\n\n const symbol = gSwatches.append('path')\n .attr('id', `${s.id}-path`)\n .attr('d', s.svg)\n .attr('transform', `\n rotate(${s.rot}, ${xRot}, ${yRot})\n translate(${xAdj} ${yAdj}) \n scale(${iScale})\n `)\n\n symbol.append('title').text(s.text)\n }\n })\n\n gSwatches.style('display', 'none')\n}\n\nexport async function trendSave(id, filename) {\n\n const svg = d3.select(`#${id} svg`)\n\n return new Promise((resolve) => {\n const blob1 = serialize(svg)\n if(filename) {\n download(blob1, filename)\n }\n resolve(blob1)\n })\n\n function download(data, filename) {\n const dataUrl = URL.createObjectURL(data)\n const file = `${filename}.svg`\n downloadLink(dataUrl, file)\n }\n\n function serialize(svg) {\n const xmlns = \"http://www.w3.org/2000/xmlns/\"\n const xlinkns = \"http://www.w3.org/1999/xlink\"\n const svgns = \"http://www.w3.org/2000/svg\"\n \n const domSvg = svg.node()\n const cloneSvg = domSvg.cloneNode(true)\n const d3Clone = d3.select(cloneSvg)\n // Explicitly change text in clone to required font\n d3Clone.selectAll('text').style('Minion Pro')\n \n cloneSvg.setAttributeNS(xmlns, \"xmlns\", svgns)\n cloneSvg.setAttributeNS(xmlns, \"xmlns:xlink\", xlinkns)\n const serializer = new window.XMLSerializer\n const string = serializer.serializeToString(cloneSvg)\n return new Blob([string], {type: \"image/svg+xml\"})\n }\n\n function downloadLink(dataUrl, file) {\n\n // Create a link element\n const link = document.createElement(\"a\")\n // Set link's href to point to the data URL\n link.href = dataUrl\n link.download = file\n\n // Append link to the body\n document.body.appendChild(link)\n\n // Dispatch click event on the link\n // This is necessary as link.click() does not work on the latest firefox\n link.dispatchEvent(\n new MouseEvent('click', { \n bubbles: true, \n cancelable: true, \n view: window \n })\n )\n // Remove link from body\n document.body.removeChild(link)\n }\n}\n","import * as d3 from 'd3'\nimport { updateTrendSummary, trendSummary } from './trendSummary'\n\nconst $ = jQuery // eslint-disable-line no-undef\nconst ds = drupalSettings // eslint-disable-line no-undef\n\nexport function bsbiExamples() {\n\n $( document ).ready(function() {\n if ($('#bsbiApparency').length) bsbiApparencyChart()\n if ($('#bsbiTimeTrend').length) bsbiSmoothedTrendChart()\n if ($('#bsbiLinearTrends').length) bsbiComaptibleLinearTrendsChart()\n if ($('#bsbiLinearSlopes').length) bsbiDensityChart()\n if ($('#bsbiSlopeClassification').length) bsbiSlopeClassChart()\n if ($('#bsbiSlopeSummary').length) bsbiSlopeSummaryChart()\n })\n}\n\nfunction bsbiApparencyChart() {\n // Apparency by latitude (PlantAltas phenology)\n d3.csv(ds.npms_vis.dataRoot + '/bsbi-demo/2cd4p9h_etk.csv', function(d){\n for (const p in d) {\n if (p !== 'type') {\n d[p] = Number(d[p]) // Ensures numeric columns from CSV are correctly typed\n }\n d.taxon = 'dummy'\n d.period = d.week\n }\n return d\n }).then(function(data){\n //console.log(data.filter(d => d.type === 'count'))\n const opts = {\n selector: '#bsbiApparency',\n data: data.filter(d => d.type === 'count'),\n taxa: ['dummy'],\n metrics: [\n { prop: '50', label: '50', colour: 'rgb(0,128,0)', fill: 'rgb(221,255,221)'},\n { prop: '51', label: '51', colour: 'rgb(0,128,0)', fill: 'rgb(221,255,221)'},\n { prop: '52', label: '52', colour: 'rgb(0,128,0)', fill: 'rgb(221,255,221)'},\n { prop: '53', label: '53', colour: 'rgb(0,128,0)', fill: 'rgb(221,255,221)'},\n { prop: '54', label: '54', colour: 'rgb(0,128,0)', fill: 'rgb(221,255,221)'},\n { prop: '55', label: '55', colour: 'rgb(0,128,0)', fill: 'rgb(221,255,221)'},\n { prop: '56', label: '56', colour: 'rgb(0,128,0)', fill: 'rgb(221,255,221)'},\n { prop: '57', label: '57', colour: 'rgb(0,128,0)', fill: 'rgb(221,255,221)'},\n { prop: '58', label: '58', colour: 'rgb(0,128,0)', fill: 'rgb(221,255,221)'},\n { prop: '59', label: '59', colour: 'rgb(0,128,0)', fill: 'rgb(221,255,221)'},\n { prop: '60', label: '60', colour: 'rgb(0,128,0)', fill: 'rgb(221,255,221)'},\n ],\n showLegend: false,\n showTaxonLabel: false,\n interactivity: 'mousemove',\n width: 350,\n height: 400,\n perRow: 1,\n expand: false,\n //missingValues: 'break', \n metricExpression: '',\n minMaxY: null,\n minY: 0,\n lineInterpolator: 'curveMonotoneX',\n chartStyle: 'area',\n composition: 'spread',\n periodType: 'week',\n axisLeftLabel: 'Latitude band',\n margin: {left: 40, right: 0, top: 0, bottom: 15},\n }\n brccharts.temporal(opts)\n })\n}\n\nfunction bsbiSmoothedTrendChart() {\n // Smooth time trend chart (Fig 1 in PlantAltas trends)\n const pBsbiGam = d3.csv(ds.npms_vis.dataRoot + '/bsbi-demo/cornish-heath-gb-gam.csv', function(d){\n for (const p in d) {\n if (p !== 'ddbid') {\n d[p] = Number(d[p]) // Ensures numeric columns from CSV are correctly typed\n }\n d.taxon = 'dummy'\n d.period = d.year\n }\n return d\n })\n\n const pBsbiMeans = d3.csv(ds.npms_vis.dataRoot + '/bsbi-demo/cornish-heath-gb-mean-sd.csv', function(d){\n for (const p in d) {\n if (p !== 'ddbid') {\n d[p] = Number(d[p]) // Ensures numeric columns from CSV are correctly typed\n }\n d.period = d.year\n d.taxon = 'dummy'\n d.y = d.mean\n d.upper = d.mean + d.std\n d.lower = d.mean - d.std\n }\n return d\n })\n \n \n Promise.all([pBsbiGam, pBsbiMeans]).then(function(data){\n console.log(data[0])\n const opts = {\n selector: '#bsbiTimeTrend',\n data: data[0],\n dataPoints: data[1],\n taxa: ['dummy'],\n metrics: [\n { prop: 'x50', strokeWidth: 2, bandUpper: 'x95', bandLower: 'x5', colour: 'blue', bandFill: 'silver', bandFillOpacity: 0.1, bandStrokeOpacity: 0.3},\n ],\n showLegend: false,\n showTaxonLabel: false,\n interactivity: 'none',\n width: 350,\n height: 250,\n perRow: 1,\n expand: false,\n //missingValues: 'break', \n metricExpression: '',\n minMaxY: null,\n periodType: 'year',\n lineInterpolator: 'curveMonotoneX',\n chartStyle: 'line',\n //composition: '',\n axisLeftLabel: 'Relative frequency',\n axisTop: 'on',\n axisRight: 'on',\n margin: {left: 45, right: 0, top: 0, bottom: 15},\n xPadPercent: 5,\n yPadPercent: 5\n }\n brccharts.temporal(opts)\n })\n}\n\nfunction bsbiComaptibleLinearTrendsChart() {\n // Compatible linear trends (Fig 2 in PlantAltas trends)\n const pBsbiMeans = d3.csv(ds.npms_vis.dataRoot + '/bsbi-demo/cornish-heath-gb-mean-sd.csv', function(d){\n for (const p in d) {\n if (p !== 'ddbid') {\n d[p] = Number(d[p]) // Ensures numeric columns from CSV are correctly typed\n }\n d.period = d.year\n d.taxon = 'dummy'\n d.y = d.mean\n d.upper = d.mean + d.std\n d.lower = d.mean - d.std\n }\n return d\n })\n \n const pBsbiLinmod = d3.csv(ds.npms_vis.dataRoot + '/bsbi-demo/cornish-heath-gb-linmod.csv', function(d){\n for (const p in d) {\n if (p !== 'ddbid') {\n d[p] = Number(d[p]) // Ensures numeric columns from CSV are correctly typed\n }\n d.taxon = 'dummy'\n d.colour = 'blue'\n d.width = 2\n d.opacity = 0.05\n }\n return d\n })\n \n Promise.all([pBsbiLinmod, pBsbiMeans]).then(function(data){\n const opts = {\n selector: '#bsbiLinearTrends',\n dataTrendLines: data[0],\n dataPoints: data[1],\n taxa: ['dummy'],\n showLegend: false,\n showTaxonLabel: false,\n interactivity: 'none',\n width: 350,\n height: 250,\n perRow: 1,\n expand: false,\n //missingValues: 'break', \n metricExpression: '',\n minMaxY: null,\n periodType: 'year',\n lineInterpolator: 'curveMonotoneX',\n chartStyle: 'line',\n //composition: '',\n axisLeftLabel: 'Relative frequency',\n axisTop: 'on',\n axisRight: 'on',\n margin: {left: 45, right: 0, top: 0, bottom: 15},\n xPadPercent: 5,\n yPadPercent: 5,\n minPeriod: 1949, \n maxPeriod: 2020\n }\n brccharts.temporal(opts)\n })\n}\n\nfunction bsbiDensityChart() {\n // Distribution of linear slope estimates (Fig 3 in PlantAltas trends)\n const pLinmod = d3.csv(ds.npms_vis.dataRoot + '/bsbi-demo/cornish-heath-gb-linmod.csv', function(d){\n return {\n gradient: Number(d.gradient),\n intercept: Number(d.intercept)\n }\n })\n const pLinmodMeans = d3.csv(ds.npms_vis.dataRoot + '/bsbi-demo/mean-gradients-gb-linmod.csv', function(d){\n return {\n slope: Number(d.gradient)\n }\n })\n const pLinmodCentiles = d3.csv(ds.npms_vis.dataRoot + '/bsbi-demo/centiles-linmod.csv', function(d){\n return {\n region: d.region,\n c5: Number(d.c5),\n c95: Number(d.c95)\n }\n })\n\n Promise.all([pLinmod, pLinmodMeans, pLinmodCentiles]).then(function(data){\n \n const density = brccharts.density({\n selector: '#bsbiLinearSlopes',\n data: [],\n ylines:[],\n xlines:[],\n width: 350,\n height: 250,\n padding: 0.1,\n margin: {left: 50, right: 10, top: 15, bottom: 40},\n expand: false,\n axisLeft: 'on',\n axisBottom: 'tick',\n axisRight: 'on',\n axisTop: 'on',\n axisLeftLabel: 'Density',\n axisBottomLabel: 'Slope',\n axisLabelFontSize: 12,\n styles: [{stroke: 'blue', strokeWidth: 1}, {stroke: 'grey', strokeWidth: 1}]\n })\n\n const xlines = [\n {x: -0.004, stroke: 'silver', strokeWidth: 1, strokeDasharray: '3 3'},\n {x: -0.001, stroke: 'silver', strokeWidth: 1, strokeDasharray: '3 3'},\n {x: 0, stroke: 'silver', strokeWidth: 1},\n {x: 0.001, stroke: 'silver', strokeWidth: 1, strokeDasharray: '3 3'},\n {x: 0.004, stroke: 'silver', strokeWidth: 1, strokeDasharray: '3 3'}\n ]\n\n const linmodData = data[0]\n const meanLinmodData = data[1]\n const linmodCentiles = data[2]\n const densityData = [linmodData.map(d => {return {slope: Number(d.gradient)}})]\n densityData.push(meanLinmodData)\n const limmodCentile = linmodCentiles.find(l => l.region === 'Britain')\n\n // In general, density slopes of individual taxon will be within the overall\n // density slope. But for outliers, in PlantAltas we adjust the x axis limits based on\n // the density slope for the taxon. Ignored here.\n //const dd = densityData[0].map(d => d.slope)\n const dp95 = 0 //stats.percentile(dd, 0.95)\n const dp5 = 1 //stats.percentile(dd, 0.05)\n const xmax = limmodCentile.c95 > dp95 ? limmodCentile.c95 : dp95\n const xmin = limmodCentile.c5 < dp5 ? limmodCentile.c5 : dp5\n density.updateChart(densityData, xmin, xmax, xlines, null, true)\n })\n}\n\nfunction bsbiSlopeClassChart() {\n\n d3.csv(ds.npms_vis.dataRoot + '/bsbi-demo/cornish-heath-gb-trend-summaries.csv', function(d){\n \n \n return [\n {value: Number(d.declineStrong), label: 'Strong decline', stroke: 'grey', strokeWidth: 1, fill: 'rgb(230,230,230)'},\n {value: Number(d.declineMod), label: 'Moderate decline', stroke: 'grey', strokeWidth: 1, fill: 'rgb(230,230,230)'},\n {value: Number(d.stable), label: 'Stable', stroke: 'grey', strokeWidth: 1, fill: 'rgb(230,230,230)'},\n {value: Number(d.increaseMod), label: 'Moderate increase', stroke: 'grey', strokeWidth: 1, fill: 'rgb(230,230,230)'},\n {value: Number(d.increaseStrong), label: 'Strong increase', stroke: 'grey', strokeWidth: 1, fill: 'rgb(230,230,230)'}\n ]\n }).then(function(data){\n\n brccharts.bar({\n selector: '#bsbiSlopeClassification',\n data: data[0],\n width: 350,\n height: 250,\n padding: 0.1,\n margin: {left: 50, right: 10, top: 10, bottom: 90},\n expand: false,\n axisLeft: 'tick',\n axisBottom: 'tick',\n axisRight: 'none',\n axisTop: 'none',\n axisLeftLabel: 'Frequency',\n axisLabelFontSize: 12,\n labelPosition: {'text-anchor': 'end', dx: '-1em', dy: '0.2em', transform: 'rotate(-55)'},\n tooltip: true\n })\n })\n}\n\nfunction bsbiSlopeSummaryChart() {\n d3.csv(ds.npms_vis.dataRoot + '/bsbi-demo/cornish-heath-gb-trend-summaries.csv', function(data){\n trendSummary('bsbiSlopeSummary')\n updateTrendSummary('bsbiSlopeSummary', data)\n })\n}\n","\nimport * as d3 from 'd3'\n\nconst $ = jQuery // eslint-disable-line no-undef\nconst ds = drupalSettings // eslint-disable-line no-undef\n\nexport function npmsTrends1() {\n $( document ).ready(function() {\n if ($('#npmsTrends1').length) {\n npmsTrends1Gui()\n createChart()\n createImage()\n }\n })\n}\n\nlet dataCsvs\nlet chart\nlet $image\nlet $imageCaption\n\nconst lowlight = 'rgb(180,180,180)'\n\nasync function npmsTrends1Gui() {\n \n $('#npmsTrends1').html('')\n\n // Top level flex layout\n const $flexGuiTop = $('