Hi data visualization lovers,
with the following code:
this.svg
.append("path")
.datum(this.data)
.attr("transform", this.x_translate)
.attr("fill", this.obs_data_color)
.attr("stroke", "none")
.attr("fill-opacity", opacity)
.attr("stroke-width", 0)
.attr(
"d",
d3
.area()
.curve(curve)
.x((d) => {
return this.x_scale(new Date(d.time));
})
.y0((d) => {
return this.y_scale(d.climate_data);
})
.y1((d) => {
return this.y_scale(d.obs_data);
})
But I would like to set different colors, one is above the green line, the other is below.
Does anyone know how to fix this? Any hints will be appreciated. Thank you!
CodePudding user response:
Here is an example that uses clipPaths, based on this difference chart by Mike Bostock.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="https://d3js.org/d3.v7.js"></script>
</head>
<body>
<div id="chart"></div>
<script>
// set up
const margin = { top: 10, right: 10, bottom: 50, left: 50 };
const width = 500 - margin.left - margin.right;
const height = 300 - margin.top - margin.bottom;
const svg = d3.select('#chart')
.append('svg')
.attr('width', width margin.left margin.right)
.attr('height', height margin.top margin.bottom)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
// data
const parseTime = d3.timeParse('%Y-%m-%d');
const data = [
{ time: "2021-12-16", obs_data: 22.2, climate_data: 18.21 },
{ time: "2021-12-17", obs_data: 18.5, climate_data: 17.59 },
{ time: "2021-12-18", obs_data: 15.4, climate_data: 17.84 },
{ time: "2021-12-19", obs_data: 17.3, climate_data: 17.67 },
{ time: "2021-12-20", obs_data: 19.7, climate_data: 18.31 },
{ time: "2021-12-21", obs_data: 18.6, climate_data: 17.59 },
{ time: "2021-12-22", obs_data: 17.7, climate_data: 17.56 },
{ time: "2021-12-23", obs_data: 20, climate_data: 17.71 },
{ time: "2021-12-24", obs_data: 19.4, climate_data: 17.82 },
{ time: "2021-12-25", obs_data: 16.4, climate_data: 17.7 },
{ time: "2021-12-26", obs_data: 13.9, climate_data: 17.58 },
{ time: "2021-12-27", obs_data: 13.1, climate_data: 17.34 },
{ time: "2021-12-28", obs_data: 16.7, climate_data: 17.13 },
{ time: "2021-12-29", obs_data: 17.8, climate_data: 17.14 },
{ time: "2021-12-30", obs_data: 16, climate_data: 16.81 },
{ time: "2021-12-31", obs_data: 16, climate_data: 15.86 },
{ time: "2022-01-01", obs_data: 16.9, climate_data: 16.37 },
{ time: "2022-01-02", obs_data: 16.9, climate_data: 17.09 },
{ time: "2022-01-03", obs_data: 18.6, climate_data: 17.68 },
{ time: "2022-01-04", obs_data: 18, climate_data: 17.56 },
{ time: "2022-01-05", obs_data: 19.3, climate_data: 17.13 },
{ time: "2022-01-06", obs_data: 16.8, climate_data: 17.3 },
{ time: "2022-01-07", obs_data: 16.1, climate_data: 17.19 },
{ time: "2022-01-08", obs_data: 16.5, climate_data: 16.54 },
{ time: "2022-01-09", obs_data: 17.6, climate_data: 16.3 },
{ time: "2022-01-10", obs_data: 17.4, climate_data: 16.95 },
{ time: "2022-01-11", obs_data: 13.8, climate_data: 17.26 },
{ time: "2022-01-12", obs_data: 13.3, climate_data: 16.63 },
{ time: "2022-01-13", obs_data: 14, climate_data: 16.15 },
{ time: "2022-01-14", obs_data: 15.3, climate_data: 16.15 },
{ time: "2022-01-15", obs_data: 16.9, climate_data: 16.16 }
].map(({time, obs_data, climate_data}) => ({ time: parseTime(time), obs_data, climate_data }));
// scales
const x = d3.scaleTime()
.domain(d3.extent(data, d => d.time))
.range([0, width]);
const y = d3.scaleLinear()
.domain(d3.extent(data.flatMap(d => [d.obs_data, d.climate_data]))).nice()
.range([height, 0]);
// area generators
// from the top of the chart to the line for climate
const topToClimate = d3.area()
.x(d => x(d.time))
.y0(0)
.y1(d => y(d.climate_data))
.curve(d3.curveMonotoneX);
// from the bottom of the chart to the line for climate
const bottomToClimate = d3.area()
.x(d => x(d.time))
.y0(height)
.y1(d => y(d.climate_data))
.curve(d3.curveMonotoneX);
// from the top of the chart to the line for obs
const topToObs = d3.area()
.x(d => x(d.time))
.y0(0)
.y1(d => y(d.obs_data))
.curve(d3.curveMonotoneX);
// from the bottom of the chart to the line for obs
const bottomToObs = d3.area()
.x(d => x(d.time))
.y0(height)
.y1(d => y(d.obs_data))
.curve(d3.curveMonotoneX);
// clip paths
svg.append('clipPath')
.attr('id', 'topToObs')
.append('path')
.attr('d', topToObs(data));
svg.append('clipPath')
.attr('id', 'bottomToObs')
.append('path')
.attr('d', bottomToObs(data));
// areas
// draw a blue area from the bottom of the chart to the blue line for climate.
// the clip path makes any part of this area outside of the clip path invisible.
// the clip path goes from the top of the chart to the red line for obs.
// the result is that you can only see the blue area when it is above the obs
// line and beneath the climate line.
svg.append('path')
.attr('fill', 'blue')
.attr('opacity', 0.6)
.attr('clip-path', 'url(#topToObs)')
.attr('d', bottomToClimate(data));
// draw a red area from the top of the chart to the blue line for climate.
// the clip path makes any part of this area outside of the clip path invisible.
// the clip path goes from the bottom of the chart to the red line for obs.
// the result is that you can only see the read area when it is above the climate
// line and beneath the obs line.
svg.append('path')
.attr('fill', 'red')
.attr('opacity', 0.6)
.attr('clip-path', 'url(#bottomToObs)')
.attr('d', topToClimate(data));
// lines
// draw a blue line for climate
svg.append('path')
.attr('stroke', 'blue')
.attr('fill', 'none')
.attr('d', bottomToClimate.lineY1()(data));
// draw a red line for obs
svg.append('path')
.attr('stroke', 'red')
.attr('fill', 'none')
.attr('d', bottomToObs.lineY1()(data));
// axes
svg.append('g')
.attr('transform', `translate(0,${height})`)
.call(d3.axisBottom(x).ticks(5, '%b %d'));
svg.append('g')
.call(d3.axisLeft(y));
</script>
</body>
</html>


