Published
Edited
Jun 26, 2020
1 star
Insert cell
Insert cell
Insert cell
Insert cell
async function drawChart() {
const temperatureMaxAccessor = d => d.maxDayTemp;
const temperatureMinAccessor = d => d.minDayTemp;
const dateParser = d3.timeParse("%Y/%m/%d");
const dateAccessor = d => dateParser(d.date);
const gradientColorScale = d3.interpolateBuPu;
const uvAccessor = d => d.meanHI;
const precipitationProbabilityAccessor = d => d.meanRain;
const cloudAccessor = d => d.meanHumidity;
const pressMinAccessor = d => d.minPress;
const pressMaxAccessor = d => d.maxPress;

const width = 800;
let dimensions = {
width: width,
height: width,
radius: width / 2,
margin: {
top: 180,
right: 150,
bottom: 150,
left: 150
}
};

dimensions.boundedWidth =
dimensions.width - dimensions.margin.left - dimensions.margin.right;
dimensions.boundedHeight =
dimensions.height - dimensions.margin.top - dimensions.margin.bottom;
dimensions.boundedRadius =
dimensions.radius - (dimensions.margin.left + dimensions.margin.right) / 2;

const getCoordinatesForAngle = (angle, offset = 1) => [
Math.cos(angle - Math.PI / 2) * dimensions.boundedRadius * offset,
Math.sin(angle - Math.PI / 2) * dimensions.boundedRadius * offset
];

const angleScale = d3
.scaleTime()
.domain(d3.extent(data, dateAccessor))
.range([0, Math.PI * 2]);

const getYFromDataPoint = (d, offset = 1.4) =>
getCoordinatesForAngle(angleScale(dateAccessor(d)), offset)[1];

const getXFromDataPoint = (d, offset = 1.4) =>
getCoordinatesForAngle(angleScale(dateAccessor(d)), offset)[0];

const radiusScale = d3
.scaleLinear()
.domain(
d3.extent([
...data.map(temperatureMinAccessor),
...data.map(temperatureMaxAccessor)
])
)
.range([0, dimensions.boundedRadius])
.nice();

const temperatureColorScale = d3
.scaleSequential()
.domain(
d3.extent([
...data.map(temperatureMinAccessor),
...data.map(temperatureMaxAccessor)
])
)
.interpolator(gradientColorScale);

const wrapper = d3
.select("#wrapper")
.append("svg")
.attr("width", dimensions.width)
.attr("height", dimensions.height);

const bounds = wrapper
.append("g")
.style(
"transform",
`translate(${dimensions.margin.left +
dimensions.boundedRadius}px, ${dimensions.margin.top +
dimensions.boundedRadius}px)`
);

const defs = wrapper.append("defs");
const gradientId = "temperature-gradient";
const gradient = defs.append("radialGradient").attr("id", gradientId);
const numberOfStops = 10;

d3.range(numberOfStops).forEach(i => {
gradient
.append("stop")
.attr("offset", `${(i * 100) / (numberOfStops - 1)}%`)
.attr("stop-color", gradientColorScale(i / (numberOfStops - 1)));
});

// Peripherals
const peripherals = bounds.append("g");

const months = d3.timeMonths(...angleScale.domain());

months.forEach(month => {
const angle = angleScale(month);
const [x, y] = getCoordinatesForAngle(angle);

peripherals
.append("line")
.attr("x2", x)
.attr("y2", y)
.attr("class", "grid-line");

const [labelX, labelY] = getCoordinatesForAngle(angle, 1.5);
peripherals
.append("text")
.attr("x", labelX)
.attr("y", labelY)
.attr("class", "tick-label")
.text(d3.timeFormat("%b")(month))
.style(
"text-anchor",
Math.abs(labelX) < 5 ? "middle" : labelX > 0 ? "start" : "end"
);
});

const temperatureTicks = radiusScale.ticks(5);

const gridCircles = temperatureTicks.map(d =>
peripherals
.append("circle")
.attr("r", radiusScale(d))
.attr("class", "grid-line")
);
const tickLabelBackgrounds = temperatureTicks.map(d => {
if (!d) return;
return peripherals
.append("rect")
.attr("y", -radiusScale(d) - 10)
.attr("width", 40)
.attr("height", 20)
.attr("fill", "#f8f9fa");
});
const tickLabels = temperatureTicks.map(d => {
if (!d) return;
return peripherals
.append("text")
.attr("x", 4)
.attr("y", -radiusScale(d) + 2)
.attr("class", "tick-label-temperature")
.text(`${d3.format(".0f")(d)}°C`);
});

const containsFreezing = radiusScale.domain()[0] < 0;
if (containsFreezing) {
const freezingCircle = bounds
.append("circle")
.attr("r", radiusScale(0))
.attr("class", "freezing-circle");
}

const areaGenerator = d3
.areaRadial()
.angle(d => angleScale(dateAccessor(d)))
.innerRadius(d => radiusScale(temperatureMinAccessor(d)))
.outerRadius(d => radiusScale(temperatureMaxAccessor(d)));

const area = bounds
.append("path")
.attr("class", "area")
.attr("d", areaGenerator(data))
.style("fill", `url(#${gradientId})`);

// UV Stuff
// const uvIndexThreshold = 6;
//const uvGroup = bounds.append("g");
// const uvOffset = 0.95;
// const highUvDays = uvGroup
// .selectAll("line")
// .data(data.filter(d => uvAccessor(d) > uvIndexThreshold))
// .enter()
// .append("line")
// .attr("class", "uv-line")
// .attr("x1", d => getXFromDataPoint(d, uvOffset))
// .attr("x2", d => getXFromDataPoint(d, uvOffset + 0.1))
// .attr("y1", d => getYFromDataPoint(d, uvOffset))
// .attr("y2", d => getYFromDataPoint(d, uvOffset + 0.1));

// Precipitation
const precipitationRadiusScale = d3
.scaleSqrt()
.domain(d3.extent(data, precipitationProbabilityAccessor))
.range([1, 20]);

const precipitationTypeColorScale = d3
.scaleOrdinal()
.domain(d3.extent(data, precipitationProbabilityAccessor))
.range(["#54a0ff"]);

const precipitationGroup = bounds.append("g");
const precipitationOffset = 1.14;
const precipitationDots = precipitationGroup
.selectAll("circle")
.data(data.filter(precipitationProbabilityAccessor))
.enter()
.append("circle")
.attr("class", "precipitation-dot")
.attr("cx", d => getXFromDataPoint(d, precipitationOffset))
.attr("cy", d => getYFromDataPoint(d, precipitationOffset))
.attr("r", d =>
precipitationRadiusScale(precipitationProbabilityAccessor(d))
)
.style("fill", d =>
precipitationTypeColorScale(precipitationProbabilityAccessor(d))
);

//cloud
//const cloudRadiusScale = d3
// .scaleSqrt()
// .domain(d3.extent(data, cloudAccessor))
// .range([1, 10]);

//const cloudGroup = bounds.append("g");
//const cloudOffset = 1.27;
//const cloudDots = cloudGroup
// .selectAll("circle")
// .data(data)
// .enter()
// .append("circle")
// .attr("class", "cloud-dot")
// .attr("cx", d => getXFromDataPoint(d, cloudOffset))
// .attr("cy", d => getYFromDataPoint(d, cloudOffset))
// .attr("r", d => cloudRadiusScale(cloudAccessor(d)));

//pressure

const pressGroup = bounds.append("g");
const pressOffset = 1.4;

const pressScale = d3
.scaleLinear()
.domain(
d3.extent([...data.map(pressMinAccessor), ...data.map(pressMaxAccessor)])
)
.range([dimensions.boundedWidth - 200, dimensions.boundedWidth - 150])
.nice();

const pressGenerator = d3
.areaRadial()
.angle(d => angleScale(dateAccessor(d)))
.innerRadius(d => pressScale(pressMinAccessor(d)))
.outerRadius(d => pressScale(pressMaxAccessor(d)));

const pressBars = pressGroup
.append("path")
.attr("class", "press")
.attr("d", pressGenerator(data))
.style('fill', d => color(d.meanPress));
// .style("fill", `url(#${gradientId})`);

// Annotations
const annotationGroup = bounds.append("g");

const drawAnnotation = (angle, offset, text) => {
const [x1, y1] = getCoordinatesForAngle(angle, offset);
const [x2, y2] = getCoordinatesForAngle(angle, 1.6);

annotationGroup
.append("line")
.attr("class", "annotation-line")
.attr("x1", x1)
.attr("x2", x2)
.attr("y1", y1)
.attr("y2", y2);

annotationGroup
.append("text")
.attr("class", "annotation-text")
.attr("x", x2 + 6)
.attr("y", y2)
.text(text);
};

drawAnnotation(Math.PI * 0.23, pressOffset, "Pressure");
drawAnnotation(Math.PI * 0.26, precipitationOffset, "Precipitation");
//drawAnnotation(
// Math.PI * 0.734,
// uvOffset + 0.05,
// `Heat Index over ${uvIndexThreshold}`
//);

drawAnnotation(Math.PI * 0.7, 0.5, "Temperature");
if (containsFreezing) {
drawAnnotation(
Math.PI * 0.9,
radiusScale(0) / dimensions.boundedRadius,
"Freezing Temperatures"
);
}
}
Insert cell
color = d3.scaleLinear(
colorDomain,
d3.quantize(d3.interpolateSpectral, 7).reverse()
)
Insert cell
colorDomain = {
const extent = d3.extent(data, d => d.meanPress),
interpolated = d3.interpolate(...extent);

return d3.quantize(interpolated, 7);
}
Insert cell
_arc = d3
.arc()
.innerRadius(d => _yScale(d.min))
.outerRadius(d => _yScale(d.max))
.startAngle(d => xScale(d.date))
.endAngle(d => xScale(d.date) + xScale.bandwidth())
.padAngle(0.01)
.padRadius(innerRadius)
Insert cell
Insert cell
innerRadius = (0.62 * width) / 2
Insert cell
Insert cell
_yScale = d3
.scaleLinear()
.domain(d3.extent(yDomain))
.range([innerRadius, _outerRadius])
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
//data = d3.csvParse(await FileAttachment("minmax_2012.csv").text(), d3.autoType)
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more