diff --git a/docs/README.md b/docs/README.md index cd981f8..871a54d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,15 +3,80 @@ ## API documentation For API details - [JSDoc API documentation](https://biologicalrecordscentre.github.io/brc-charts/docs/api/). -## Working examples +## Working chart examples +### Temporal charts +The temporal chart (`brccharts.temporal`) can be used for all sorts of charts which require +their x-axis to span either a year (e.g. for phenology) or a number of years (e.g. to visualise +the accumulation of records over time). This is probably the most widely useful chart. Examples: +- [Temporal charts](example-8.html) +- [Temporal charts interactive](example-9.html) +- [UKBMS interactive](example-ukbms-1.html) +- [UKBMS example](example-ukbms-2.html) +### Pie/donut charts +The pie/donut chart (`brccharts.pie`) does what it says on the tin. Example: - [Pie/donut charts](example-1.html) -- [Phenology charts](example-2.html) +### Phenology charts +The phenology chart (`brccharts.phen2`) is a specific type of temporal chart which +is purely for displaying phenological data. Example: +- [Phenology bands](example-10.html) +### Accumulation charts +The accumulation chart (`brccharts.accum`) was created for a very specific use case to chart record accumulation +over the course of a year, displaying both the cummulative number of taxa and +the cummulative number of records together. Example: - [Species/record accumulation charts](example-3.html) -- [Interspecific relationship charts](example-4.html) +### Trend charts +Also created for a very specific use case, the trend chart (`brccharts.trend`) displays two graphics: 1) a bar +chart showing the absolute yearly counts for each taxon shown and 2) a line graph showing, +for each year, the percetage of the sum of counts for all species in that year +which the count for the taxon represents. Two y-axes are shown (left and right) to reflect these +two metrics. - [Trend charts](example-5.html) -- [Yearly charts](example-6.html) -- [BBS style latitude vs altitude charts](example-7.html) -- [Temporal charts](example-8.html) -- [Temporal charts interactive](example-9.html) +### Latitude vs altitude charts +Another very specific use case, the latitude vs altitude chart (`brccharts.altlat`) is based on that which appears in the +[Atlas of British and irish Bryophytes](https://www.britishbryologicalsociety.org.uk/publications/atlas-of-british-and-irish-bryophytes/). +The chart can be used to visualise how tetrad occupancy varies according to both altitude and latitude across Great Britain. This was developed for the [Plant Atlas 2020 website](https://plantatlas2020.org/). +- [BBS style latitude vs altitude charts](example-7.html). + +## Deprecated charts +When the library was first published, the functionality now provided by the temporal chart (`brccharts.temporal`) +was split across two chart types: 1) the 'phenology' chart (`brccharts.phen1`) provided charting ability for metrics +across the year and 2) the 'yearly chart (`brccharts.yearly`) provided charting ability for metrics across years. These +should no longer be used in new developments. Examples: +- [Deprecated 'phen1' chart](example-2.html) +- [Deprecated 'yearly' chart](example-6.html) +The 'yearly' chart is used to provide figures 1 and 2 on the 'trends' tab of the [Plant Atlas 2020 website](https://plantatlas2020.org/) +but these can now be produced with the temporal chart (see the foot of this [temporal charts example](example-8.html)). + +## Experimental charts +The links chart (`brccharts.links`) was an experimental development to explore visualising inter-specific relationships. +It has not been developed past the point required to produce the example shown below: +- [Interspecific relationship charts](example-4.html) + +## Undocumented charts +There are a couple of undocumented charts in the libarary - i.e. they do not appear in +the [API documentation](https://biologicalrecordscentre.github.io/brc-charts/docs/api/). +These were developed for the the [Plant Atlas 2020 website](https://plantatlas2020.org/). +### Undocumented 'bar' chart +The bar chart (`brccharts.bar`) is designed to display a bar chart. +Although initiallly created to facilitate the [Plant Atlas 2020 website](https://plantatlas2020.org/) +(figure 4 on the 'trends' tab), +it has been generalised and could be used in other contexts +### Undocumented 'density' chart +The density chart (`brccharts.density`) is designed to show one or more density plots +derived from frequency data (data) +It was created to facilitate the [Plant Atlas 2020 website](https://plantatlas2020.org/) +(figure 3 on the 'trends' tab) +and although some features have been generalised, it contains some +code that is specific for that use case. + +[[Back to repo]](https://github.com/BiologicalRecordsCentre/brc-charts) + + + + + + + + + -[[Back to repo]](https://github.com/BiologicalRecordsCentre/brc-charts) \ No newline at end of file diff --git a/index.js b/index.js index 47e7b10..9656ec9 100644 --- a/index.js +++ b/index.js @@ -4,8 +4,6 @@ import { phen2 } from './src/phen2' import { accum } from './src/accum' import { links } from './src/links' import { trend } from './src/trend' -import { trend2 } from './src/trend2/trend2' -import { trend3 } from './src/trend3/trend3' import { density } from './src/density/density' import { bar } from './src/bar/bar' import { yearly } from './src/yearly/yearly' @@ -24,8 +22,6 @@ export { accum, links, trend, - trend2, - trend3, density, bar, yearly, diff --git a/src/trend2/trend2.js b/src/trend2/trend2.js deleted file mode 100644 index dbe1927..0000000 --- a/src/trend2/trend2.js +++ /dev/null @@ -1,374 +0,0 @@ -// README (updated 15/07/2022) -// This chart is designed to show a trend line with a confidence band (data) -// and, optionally, mean points with confidence bars (means). -// Although many features of it have been generalised, it was -// initially constructed to facilitate the BSBI atlas website and is -// currently undocumented. - -// THE FUNCTIONALITY IN THIS CHART IS NOW SUPERCEEDED BY THE TEMPORAL CHART. - -import * as d3 from 'd3' -import { xAxisYear } from '../general' - -export function trend2({ - // Default options in here - selector = 'body', - elid = 'trend2-chart', - width = 300, - height = 200, - margin = {left: 35, right: 0, top: 20, bottom: 5}, - expand = false, - axisLabelFontSize = 10, - axisLeft = 'tick', - axisBottom = 'tick', - axisRight = '', - axisTop = '', - axisLeftLabel = '', - duration = 1000, - yearMin = null, - yearMax = null, - yMin = null, - yMax = null, - adjust = true, - ylines = [], - data = [], - means = [], - style = {} -} = {}) { - - // Ensure default style properties are present - style.vStroke = style.vStroke ? style.vStroke : 'black' - style.vStrokeWidth = style.vStrokeWidth ? style.vStrokeWidth : 2 - style.cStroke = style.cStroke ? style.cStroke : 'black' - style.cStrokeWidth = style.cStrokeWidth ? style.cStrokeWidth : 1 - style.cFill = style.cFill ? style.cFill : 'silver' - style.mFill = style.mFill ? style.mFill : 'white' - style.mRad = style.mRad ? style.mRad : 2 - style.mStroke = style.mStroke ? style.mStroke : 'black' - style.mStrokeWidth = style.mStrokeWidth ? style.mStrokeWidth : 1 - style.sdStroke = style.sdStroke ? style.sdStroke : 'black' - style.sdStrokeWidth = style.sdStrokeWidth ? style.sdStrokeWidth : 1 - - const updateChart = makeChart(yMin, yMax, adjust, yearMin, yearMax, data, means, ylines, selector, elid, width, height, margin, expand, axisLeft, axisRight, axisTop, axisBottom, axisLeftLabel, axisLabelFontSize, duration, style) - - return { - updateChart: updateChart - } -} - -function maxYear(data) { - return Math.max(...data.map(d => d.year)) -} -function minYear(data) { - return Math.min(...data.map(d => d.year)) -} -function maxY(data, means) { - const dMax = Math.max(...data.map(d => d.upper ? d.upper : d.value)) - const mMax = Math.max(...means.map(d => d.mean + d.sd)) - return Math.max(dMax, mMax) -} -function minY(data, means) { - const dMin = Math.min(...data.map(d => d.lower ? d.lower : d.value)) - const mMin = Math.min(...means.map(d => d.mean - d.sd)) - return Math.min(dMin, mMin) -} - -function makeChart(yMin, yMax, adjust, yearMin, yearMax, data, means, ylines, selector, elid, width, height, margin, expand, axisLeft, axisRight, axisTop, axisBottom, axisLeftLabel, axisLabelFontSize, duration, style) { - - const svgWidth = width + margin.left + margin.right - const svgHeight = height + margin.top + margin.bottom - - // Append the chart svg - const svgTrend = d3.select(`${selector}`) - .append('svg') - .attr('id', elid) - - // Size the chart svg - if (expand) { - svgTrend.attr("viewBox", `0 0 ${svgWidth} ${svgHeight}`) - } else { - svgTrend.attr("width", svgWidth) - svgTrend.attr("height", svgHeight) - } - - // Axis labels - if (axisLeftLabel) { - svgTrend.append("text") - .attr("transform", `translate(${axisLabelFontSize},${margin.top + height/2}) rotate(270)`) - .style("text-anchor", "middle") - .style('font-size', axisLabelFontSize) - .text(axisLeftLabel) - } - - // Create axes and position within SVG - let tAxis, bAxis, lAxis, rAxis - if (axisLeft === 'on' || axisLeft === 'tick') { - lAxis = svgTrend.append("g") - .attr("transform", `translate(${margin.left},${margin.top})`) - } - if (axisBottom === 'on' || axisBottom === 'tick') { - bAxis = svgTrend.append("g") - .attr("transform", `translate(${margin.left},${margin.top + height})`) - } - if (axisTop === 'on') { - tAxis = svgTrend.append("g") - .attr("transform", `translate(${margin.left},${margin.top})`) - } - if (axisRight === 'on') { - rAxis = svgTrend.append("g") - .attr("transform", `translate(${margin.left + width}, ${margin.top})`) - } - - // Create g element for chart elements - const gChart1 = svgTrend.append("g") - .attr("transform", `translate(${margin.left},${margin.top})`) - const gChart2 = svgTrend.append("g") - .attr("transform", `translate(${margin.left},${margin.top})`) - - // Create the API function for updating chart - const updateChart = makeUpdateChart(svgTrend, width, height, tAxis, bAxis, lAxis, rAxis, axisBottom, duration, gChart1, gChart2, style) - - // Update the chart with current data - updateChart(data, means, yearMin, yearMax, yMin, yMax, adjust, ylines) - - // Return the api - return updateChart -} - -function makeUpdateChart( - svg, - width, - height, - tAxis, - bAxis, - lAxis, - rAxis, - axisBottom, - duration, - gChart1, - gChart2, - style -) { - return (data, means, yearMin, yearMax, yMin, yMax, adjust, ylines) => { - - // Set ylines to empty array if not set - if (!ylines) { - ylines = [] - } - - // Data - const dataWork = data.sort((a, b) => (a.year > b.year) ? 1 : -1) - - // Adjustments - let yMinBuff, yMaxBuff - if (yMin !== null && yMax !== null && typeof yMin !== 'undefined' && typeof yMax !== 'undefined') { - yMinBuff = yMin - yMaxBuff = yMax - if (adjust) { - if (minY(dataWork, means) < yMinBuff) yMinBuff = minY(dataWork, means) - if (maxY(dataWork, means) > yMaxBuff) yMaxBuff = maxY(dataWork, means) - // Add a margin to min/max values - yMinBuff = yMinBuff - (yMaxBuff - yMinBuff) / 50 - yMaxBuff = yMaxBuff + (yMaxBuff - yMinBuff) / 50 - } - - } else { - yMinBuff = minY(dataWork, means) - yMaxBuff = maxY(dataWork, means) - // Add a margin to min/max values - yMinBuff = yMinBuff - (yMaxBuff - yMinBuff) / 50 - yMaxBuff = yMaxBuff + (yMaxBuff - yMinBuff) / 50 - } - - const yearMinData = minYear(dataWork) - const yearMaxData = maxYear(dataWork) - let yearMinBuff, yearMaxBuff - if (yearMin) { - yearMinBuff = yearMin - } else { - yearMinBuff = Math.floor(yearMinData - (yearMaxData - yearMinData) / 50) - } - if (yearMax) { - yearMaxBuff = yearMax - } else { - yearMaxBuff = Math.floor(yearMaxData + (yearMaxData - yearMinData) / 50) - } - - // Value scales - const xScale = d3.scaleLinear().domain([yearMinBuff, yearMaxBuff]).range([0, width]) - const yScale = d3.scaleLinear().domain([yMinBuff, yMaxBuff]).range([height, 0]) - - // Generate axes - if (tAxis) { - tAxis - .call(d3.axisTop() - .scale(xScale) // Actual scale doesn't matter, but needs one - .tickValues([]) - .tickSizeOuter(0)) - } - if (bAxis) { - bAxis.transition().duration(duration) - .call(xAxisYear(width, axisBottom === 'tick', yearMinBuff, yearMaxBuff, false)) - } - if (lAxis) { - lAxis.transition().duration(duration) - .call(d3.axisLeft() - .scale(yScale) - .ticks(5)) - } - if (rAxis) { - rAxis - .call(d3.axisRight() - .scale(yScale) - .tickValues([]) - .tickSizeOuter(0)) - } - - // Line path generator - const linePath = d3.line() - //.curve(d3.curveMonotoneX) - .x(d => xScale(d.y)) - .y(d => yScale(d.v)) - - // Main data line - const vData = data.map(p => {return {y: p.year, v: p.value}}) - d3Line(gChart2, linePath, duration, vData, 'valueLine', style.vStroke, style.vStrokeWidth, 'none') - - // Upper confidence line - const uData = data.map(p => {return {y: p.year, v: p.upper}}) - d3Line(gChart2, linePath, duration, uData, 'upperLine', style.cStroke, style.cStrokeWidth, 'none') - - // Upper confidence line - const lData = data.map(p => {return {y: p.year, v: p.lower}}) - d3Line(gChart2, linePath, duration, lData, 'lowerLine', style.cStroke, style.cStrokeWidth, 'none') - - // Confidence polygon - lData.sort((a,b) => b.y - a.y) // Reverse order of lData - const pData = [...uData, ...lData] - d3Line(gChart1, linePath, duration, pData, 'confidence', 'none', 0, style.cFill) - - // Mean and SDs - const tMeans = means.map(p => { - return { - x: xScale(p.year), - y: yScale(p.mean), - bar: linePath([{y: p.year, v: p.mean-p.sd}, {y: p.year, v: p.mean+p.sd}]) - } - }) - d3MeanSd(gChart2, linePath, duration, tMeans, style) - - // Add path to ylines and generate - ylines.forEach(l => { - l.path = linePath([{y: yearMinBuff, v: l.y}, {y: yearMaxBuff, v: l.y}]) - }) - const tYlines = ylines.filter(l => l.y >= yMinBuff && l.y <= yMaxBuff) - d3Yline(gChart1, tYlines, duration) - } -} - -function d3Line(gChart, linePath, duration, data, lClass, stroke, strokeWidth, fill) { - - let aData - if (data.length === 0) { - aData = data - } else { - aData = [data] - } - - gChart.selectAll(`.${lClass}`) - .data(aData) - .join( - enter => enter.append('path') - .attr("d", d => linePath(d)) - .attr('class', lClass) - .style('fill', fill) - .style('stroke', stroke) - .style('stroke-width', strokeWidth) - .attr("opacity", 0), - update => update, - exit => exit - .transition().duration(duration) - .style("opacity", 0) - .remove() - ) - // Join returns merged enter and update selection - .transition().duration(duration) - .attr("d", d => linePath(d)) - .attr("opacity", 1) -} - -function d3MeanSd(gChart, linePath, duration, means, style) { - - //console.log(means) - //console.log(style) - - // SDs - gChart.selectAll('.sds') - .data(means) - .join( - enter => enter.append('path') - .attr('d', d => d.bar) - .attr('class', 'sds') - .style('stroke', style.sdStroke) - .style('stroke-width', style.sdStrokeWidth) - .style('opacity', 0), - update => update, - exit => exit - .transition().duration(duration) - .style("opacity", 0) - .remove() - ) - // Join returns merged enter and update selection - .transition().duration(duration) - .attr('d', d => d.bar) - .style('opacity', 1) - - // Means - gChart.selectAll('.means') - .data(means) - .join( - enter => enter.append('circle') - .attr('cx', d => d.x) - .attr('cy', d => d.y) - .attr('r', style.mRad) - .attr('class', 'means') - .style('fill', style.mFill) - .style('stroke', style.mStroke) - .style('stroke-width', style.mStrokeWidth) - .style('opacity', 0), - update => update, - exit => exit - .transition().duration(duration) - .style("opacity", 0) - .remove() - ) - // Join returns merged enter and update selection - .transition().duration(duration) - .attr('cx', d => d.x) - .attr('cy', d => d.y) - .style('opacity', 1) -} - -function d3Yline(gChart, ylines, duration) { - - // Horizontal y lines - gChart.selectAll('.ylines') - .data(ylines) - .join( - enter => enter.append('path') - .attr('d', d => d.path) - .attr('class', 'ylines') - .style('stroke', d => d.stroke) - .style('stroke-width', d => d.strokeWidth) - .style('opacity', 0), - update => update, - exit => exit - .transition().duration(duration) - .style("opacity", 0) - .remove() - ) - // Join returns merged enter and update selection - .transition().duration(duration) - .attr('d', d => d.path) - .style('opacity', 1) -} diff --git a/src/trend3/trend3.js b/src/trend3/trend3.js deleted file mode 100644 index 6413559..0000000 --- a/src/trend3/trend3.js +++ /dev/null @@ -1,333 +0,0 @@ -// README (updated 15/07/2022) -// This chart is designed to shown multiple gradient lines (data) -// and, optionally, mean points with confidence bars (means). -// Although many features of it have been generalised, it was -// initially constructed to facilitate the BSBI atlas website and is undocumented. - -// THE FUNCTIONALITY IN THIS CHART IS NOW SUPERCEEDED BY THE TEMPORAL CHART. - -import * as d3 from 'd3' -import { xAxisYear } from '../general' - -export function trend3({ - // Default options in here - selector = 'body', - elid = 'trend3-chart', - width = 300, - height = 200, - margin = {left: 35, right: 0, top: 20, bottom: 5}, - expand = false, - axisLabelFontSize = 10, - axisLeft = 'tick', - axisBottom = 'tick', - axisRight = '', - axisTop = '', - axisLeftLabel = '', - duration = 1000, - yearMin = 1949, - yearMax = 2019, - yMin = null, - yMax = null, - adjust = false, - ylines = [], - data = [], - means = [], - style = {} -} = {}) { - - // Ensure default style properties are present - style.vStroke = style.vStroke ? style.vStroke : 'black' - style.vStrokeWidth = style.vStrokeWidth ? style.vStrokeWidth : 2 - style.vOpacity = style.vOpacity ? style.vOpacity : 0.1 - style.mFill = style.mFill ? style.mFill : 'white' - style.mRad = style.mRad ? style.mRad : 2 - style.mStroke = style.mStroke ? style.mStroke : 'black' - style.mStrokeWidth = style.mStrokeWidth ? style.mStrokeWidth : 1 - style.sdStroke = style.sdStroke ? style.sdStroke : 'black' - style.sdStrokeWidth = style.sdStrokeWidth ? style.sdStrokeWidth : 1 - - const updateChart = makeChart(yMin, yMax, adjust, yearMin, yearMax, data, means, ylines, selector, elid, width, height, margin, expand, axisLeft, axisRight, axisTop, axisBottom, axisLeftLabel, axisLabelFontSize, duration, style) - - return { - updateChart: updateChart - } -} - -function maxY(data, means) { - const dMax = Math.max(...data.map(d => Math.max(d[0].v, d[1].v))) - const mMax = Math.max(...means.map(d => d.mean + d.sd)) - return Math.max(dMax, mMax) -} -function minY(data, means) { - const dMin = Math.min(...data.map(d => Math.min(d[0].v, d[1].v))) - const mMin = Math.min(...means.map(d => d.mean - d.sd)) - return Math.min(dMin, mMin) -} - -function makeChart(yMin, yMax, adjust, yearMin, yearMax, data, means, ylines, selector, elid, width, height, margin, expand, axisLeft, axisRight, axisTop, axisBottom, axisLeftLabel, axisLabelFontSize, duration, style) { - - const svgWidth = width + margin.left + margin.right - const svgHeight = height + margin.top + margin.bottom - - // Append the chart svg - const svgTrend = d3.select(`${selector}`) - .append('svg') - .attr('id', elid) - - // Size the chart svg - if (expand) { - svgTrend.attr("viewBox", `0 0 ${svgWidth} ${svgHeight}`) - } else { - svgTrend.attr("width", svgWidth) - svgTrend.attr("height", svgHeight) - } - - // Axis labels - if (axisLeftLabel) { - svgTrend.append("text") - .attr("transform", `translate(${axisLabelFontSize},${margin.top + height/2}) rotate(270)`) - .style("text-anchor", "middle") - .style('font-size', axisLabelFontSize) - .text(axisLeftLabel) - } - - // Create axes and position within SVG - let tAxis, bAxis, lAxis, rAxis - if (axisLeft === 'on' || axisLeft === 'tick') { - lAxis = svgTrend.append("g") - .attr("transform", `translate(${margin.left},${margin.top})`) - } - if (axisBottom === 'on' || axisBottom === 'tick') { - bAxis = svgTrend.append("g") - .attr("transform", `translate(${margin.left},${margin.top + height})`) - } - if (axisTop === 'on') { - tAxis = svgTrend.append("g") - .attr("transform", `translate(${margin.left},${margin.top})`) - } - if (axisRight === 'on') { - rAxis = svgTrend.append("g") - .attr("transform", `translate(${margin.left + width}, ${margin.top})`) - } - - // Create g element for chart elements - const gChart1 = svgTrend.append("g") - .attr("transform", `translate(${margin.left},${margin.top})`) - const gChart2 = svgTrend.append("g") - .attr("transform", `translate(${margin.left},${margin.top})`) - - // Create the API function for updating chart - const updateChart = makeUpdateChart(svgTrend, width, height, tAxis, bAxis, lAxis, rAxis, axisBottom, duration, gChart1, gChart2, style) - - // Update the chart with current data - updateChart(data, means, yearMin, yearMax, yMin, yMax, adjust, ylines) - - // Return the api - return updateChart -} - -function makeUpdateChart( - svg, - width, - height, - tAxis, - bAxis, - lAxis, - rAxis, - axisBottom, - duration, - gChart1, - gChart2, - style -) { - return (data, means, yearMin, yearMax, yMin, yMax, adjust, ylines) => { - - // Set ylines to empty array if not set - if (!ylines) { - ylines = [] - } - - // Convert data from an array of gradients and intercepts to an array - // of arrays of two point lines - const dataWork = data.map(d => { - const yStart = d.gradient * yearMin + d.intercept - const yEnd = d.gradient * yearMax + d.intercept - return [{y: yearMin, v: yStart}, {y: yearMax, v: yEnd}] - }) - - // Adjustments - let yMinBuff, yMaxBuff - if (yMin !== null && yMax !== null && typeof yMin !== 'undefined' && typeof yMax !== 'undefined') { - yMinBuff = yMin - yMaxBuff = yMax - if (adjust) { - if (minY(dataWork, means) < yMinBuff) yMinBuff = minY(dataWork, means) - if (maxY(dataWork, means) > yMaxBuff) yMaxBuff = maxY(dataWork, means) - // Add a margin to min/max values - yMinBuff = yMinBuff - (yMaxBuff - yMinBuff) / 50 - yMaxBuff = yMaxBuff + (yMaxBuff - yMinBuff) / 50 - } - } else { - yMinBuff = minY(dataWork, means) - yMaxBuff = maxY(dataWork, means) - // Add a margin to min/max values - yMinBuff = yMinBuff - (yMaxBuff - yMinBuff) / 50 - yMaxBuff = yMaxBuff + (yMaxBuff - yMinBuff) / 50 - } - const yearMinBuff = yearMin - const yearMaxBuff = yearMax - - // Value scales - const xScale = d3.scaleLinear().domain([yearMinBuff, yearMaxBuff]).range([0, width]) - const yScale = d3.scaleLinear().domain([yMinBuff, yMaxBuff]).range([height, 0]) - - // Generate axes - if (tAxis) { - tAxis - .call(d3.axisTop() - .scale(xScale) // Actual scale doesn't matter, but needs one - .tickValues([]) - .tickSizeOuter(0)) - } - if (bAxis) { - bAxis.transition().duration(duration) - .call(xAxisYear(width, axisBottom === 'tick', yearMinBuff, yearMaxBuff, false)) - } - if (lAxis) { - lAxis.transition().duration(duration) - .call(d3.axisLeft() - .scale(yScale) - .ticks(5)) - } - if (rAxis) { - rAxis - .call(d3.axisRight() - .scale(yScale) - .tickValues([]) - .tickSizeOuter(0)) - } - - // Line path generator - const linePath = d3.line() - //.curve(d3.curveMonotoneX) - .x(d => xScale(d.y)) - .y(d => yScale(d.v)) - - // Main data line - d3Line(gChart2, linePath, duration, dataWork, style) - - // Mean and SDs - const tMeans = means.map(p => { - return { - x: xScale(p.year), - y: yScale(p.mean), - bar: linePath([{y: p.year, v: p.mean-p.sd}, {y: p.year, v: p.mean+p.sd}]), - barStart: linePath([{y: p.year, v: yMinBuff}, {y: p.year, v: yMinBuff}]) - } - }) - d3MeanSd(gChart2, linePath, yScale(yMinBuff), duration, tMeans, style) - - // Add path to ylines and generate - ylines.forEach(l => { - l.path = linePath([{y: yearMinBuff, v: l.y}, {y: yearMaxBuff, v: l.y}]) - }) - const tYlines = ylines.filter(l => l.y >= yMinBuff && l.y <= yMaxBuff) - d3Yline(gChart1, tYlines, duration) - } -} - -function d3Line(gChart, linePath, duration, data, style) { - - gChart.selectAll('.trend-line') - .data(data) - .join( - enter => enter.append('path') - .attr("d", d => linePath(d)) - .attr('class', 'trend-line') - .style('stroke', style.vStroke) - .style('stroke-width', style.vStrokeWidth) - .attr("opacity", 0), - update => update, - exit => exit - .transition().duration(duration) - .style("opacity", 0) - .remove() - ) - // Join returns merged enter and update selection - .transition().duration(duration) - .attr("d", d => linePath(d)) - .attr("opacity", style.vOpacity) -} - -function d3MeanSd(gChart, linePath, yMinBuff, duration, means, style) { - - // SDs - gChart.selectAll('.sds') - .data(means) - .join( - enter => enter.append('path') - .attr('d', d => d.bar) - .attr('class', 'sds') - .style('stroke', style.sdStroke) - .style('stroke-width', style.sdStrokeWidth) - .style('opacity', 0), - update => update, - exit => exit - .transition().duration(duration) - .style("opacity", 0) - .remove() - ) - // Join returns merged enter and update selection - .transition().duration(duration) - .attr('d', d => d.bar) - .style('opacity', 1) - - // Means - gChart.selectAll('.means') - .data(means) - .join( - enter => enter.append('circle') - .attr('cx', d => d.x) - .attr('cy', d => d.y) - .attr('r', style.mRad) - .attr('class', 'means') - .style('fill', style.mFill) - .style('stroke', style.mStroke) - .style('stroke-width', style.mStrokeWidth) - .style('opacity', 0), - update => update, - exit => exit - .transition().duration(duration) - .style("opacity", 0) - .remove() - ) - // Join returns merged enter and update selection - .transition().duration(duration) - .attr('cx', d => d.x) - .attr('cy', d => d.y) - .style('opacity', 1) -} - -function d3Yline(gChart, ylines, duration) { - - // Horizontal y lines - gChart.selectAll('.ylines') - .data(ylines) - .join( - enter => enter.append('path') - .attr('d', d => d.path) - .attr('class', 'ylines') - .style('stroke', d => d.stroke) - .style('stroke-width', d => d.strokeWidth) - .style('opacity', 0), - update => update, - exit => exit - .transition().duration(duration) - .style("opacity", 0) - .remove() - ) - // Join returns merged enter and update selection - .transition().duration(duration) - .attr('d', d => d.path) - .style('opacity', 1) -} \ No newline at end of file