Published
Edited
Apr 1, 2022
8 stars
Insert cell
Insert cell
laxMap = chart2(lax["data"])
Insert cell
// chart3 = {
// const d = 480;
// const svg = d3.create("svg")
// .attr("cursor", "default")
// .attr("width", d)
// .attr("height", d);
// document.body.append(svg.node());
// const chart = new RingChart(svg)
// .tick({name: "Sales"})
// .size([d, d])
// .innerRadius(100)
// .options({chartType: "heatmap", nodeStyle: "arc"})
// .palette(d3.interpolateGreens) // Sequential color interpolator for heatmap
// .data(test1)
// .render();

// svg.node().remove();
// return svg.node();
// }
Insert cell
d3.extent([...new Set(test2["SIN"].map(d => d.yearday))])
Insert cell
function ramp(color, n = 512) {
const canvas = DOM.canvas(n, 1);
const context = canvas.getContext("2d");
canvas.style.margin = "0 -14px";
canvas.style.width = "calc(100% + 28px)";
canvas.style.height = "40px";
canvas.style.imageRendering = "-moz-crisp-edges";
canvas.style.imageRendering = "pixelated";
for (let i = 0; i < n; ++i) {
context.fillStyle = color(i / (n - 1));
context.fillRect(i, 0, 1, 1);
}
return canvas;
}
Insert cell
ramp(d3.interpolateCubehelixDefault) // A better cylical color scheme.
Insert cell
colorDomain = d3.extent(lax["data"].map(d => d.flights));
// console.log('___', colorDomain, data.map(d => d.flights));
// let colorScale = d3.scaleDivergingSqrt(colorDomain, d3.interpolateSpectral);
Insert cell
colorScale = d3.scaleDiverging(colorDomain, d3.interpolateHslLong("red", "blue"));
Insert cell
d3.extent(lax["data"].map(row => row.flights))
Insert cell
lax["data"].filter(row => row.flights >= 277);
Insert cell
colorLegend(lax["data"])
Insert cell
import {legend, swatches} from "@d3/color-legend"

Insert cell
colorLegend = function(data) {
const domain = d3.extent(data.map(row => row.flights))
return legend({
color: d3.scaleDiverging( domain, d3.interpolatePuBu),
title: "Colors",
// ticks: 7,
})
}
Insert cell
atlMap=chart2(atl["data"])
Insert cell
lhrMap = chart2(test3["LHR"])
Insert cell
Insert cell
function chart2(data) {
const svg = d3.select(DOM.svg(width, height))
.attr("viewBox", `${-width / 2} ${-height / 2} ${width} ${height}`)
.style("width", "100%")
.style("height", "auto")
.style("font", "10px sans-serif");

const innerRadius = 25;
const outerRadius = Math.min(width, height) / 3;
let margin = {top: 20, right: 20, bottom: 20, left: 20};
let numSegments = 365;
let segmentHeight = (outerRadius - innerRadius)/24;
let offset = innerRadius + Math.ceil(data.length / numSegments) * segmentHeight;
let radialLabels = [...new Set(data.map(d => d.hr))];
let segmentLabels = [...new Set(pge_src.map(d => daysIntoYear(d.date)))];
const g = svg.append("g")
.classed("circular-heat", true)
.attr("transform", "translate(0, 0)");
/* Arc functions */
let ir = function(d, i) {
return innerRadius + (d.hr-1) * segmentHeight;
}
let or = function(d, i) {
return innerRadius + segmentHeight * d.hr;
}

// Start angle
let sa = function(d, i) {
// return 0;
return ((d.yearday-1) * 2 * Math.PI)/365;// / numSegments;
}

// End angle
let ea = function(d, i) {
//return 1/4 * 2 * Math.PI;
//return ((d.day + 1) * 2 * Math.PI)/30;// / numSegments;
return (d.yearday * 2 * Math.PI)/365;
}
g.selectAll("path").data(data)
.enter().append("path")
.attr("d", d3.arc().innerRadius(ir).outerRadius(or).startAngle(sa).endAngle(ea))
.attr("fill", d => { return colorScale(d.flights) });

var labels = svg.append("g")
.classed("labels", true)
.classed("radial", true)
.attr("transform", "translate(0, 0)");

var lsa = 0.01; //Label start angle
var labels = svg.append("g")
.classed("labels", true)
.classed("radial", true)
.attr("transform", "translate(0,0)");

labels.selectAll("def")
.data(radialLabels).enter()
.append("def")
.append("path")
.attr("id", function(d, i) {return "radial-label-path-test-"+i;})
.attr("d", function(d, i) {
// console.log(i);
var r = innerRadius + ((i + 0.2) * segmentHeight);
return "m" + r * Math.sin(lsa) + " -" + r * Math.cos(lsa) +
" a" + r + " " + r + " 0 1 1 -1 0";
});

labels.selectAll("text")
.data(radialLabels).enter()
.append("text")
.append("textPath")
.attr("xlink:href", function(d, i) {return "#radial-label-path-test-"+i;})
.style("font-size", 0.8 * segmentHeight + 'px')
.text(function(d) {return d;});

//Segment labels
var segmentLabelOffset = 10;
// var r = innerRadius + Math.ceil(data.length / numSegments) * segmentHeight + segmentLabelOffset;
// labels = svg.append("g")
// .classed("labels", true)
// .classed("segment", true)
// .attr("transform", "translate(0, 0)");

// labels.append("def")
// .append("path")
// .attr("id", "segment-label-path-test")
// .attr("d", "m0 -" + r + " a" + r + " " + r + " 0 1 1 -1 0");

// labels.selectAll("text")
// .data(segmentLabels).enter()
// .append("text")
// .append("textPath")
// .attr("xlink:href", "#segment-label-path-test")
// .attr("startOffset", function(d, i) {return i * 100 / numSegments + "%";})
// .text(function(d) {return d;})
// .attr("font-size", 2);

let arcLabelsG = labels
.selectAll('.arc-label')
.data(arcLabels)
.enter()
.append('g')
.attr('class', 'arc-label');

let dayAngle = 360 / 365;

arcLabelsG
.append('text')
.text(function(d) {
return d.month;
})
.attr('x', function(d) {
let monthAngle = 360 * (d.days / 365);
let labelAngle = d.start * dayAngle;// + monthAngle / 5;
let labelRadius = outerRadius + segmentLabelOffset;
return labelX(labelAngle, labelRadius);
})
.attr('y', function(d) {
let monthAngle = 360 * (d.days / 365);
let labelAngle = d.start * dayAngle;// + monthAngle / 5;
let labelRadius = outerRadius + segmentLabelOffset;
return labelY(labelAngle, labelRadius);
})
.style('text-anchor', function(d, i) {
return i < arcLabels.length / 2 ? 'start' : 'end';
});

return svg.node();
}
Insert cell
chart(pge_src)
Insert cell
Insert cell
d3.extent(pge_src.map(d => d.usage))
Insert cell
// radChart = {
// const svg = d3.select(DOM.svg(width, height))
// .attr("viewBox", `${-width / 2} ${-height / 2} ${width} ${height}`)
// .style("width", "100%")
// .style("height", "auto")
// .style("font", "10px sans-serif");

// const innerRadius = 180;
// const outerRadius = Math.min(width, height) / 2

// const x = d3.scaleBand()
// .domain(pge_src.map(d => d.date.getHours()))
// .range([0, 2 * Math.PI])
// .align(0);

// // This scale maintains area proportionality of radial bars
// // change to scale
// const y = d3.scaleBand()
// .domain([1,2,3])
// //.domain([...new Set(pge_src.map(d => daysIntoYear(d.date)))])
// .range([innerRadius, outerRadius])

// const yAxis = g => g
// .attr("text-anchor", "middle")
// .call(g => g.append("text")
// .attr("y", d => -y(y.ticks(5).pop()))
// .attr("dy", "-1em")
// .text("Population"))
// .call(g => g.selectAll("g")
// .data(y.ticks(5).slice(1))
// .join("g")
// .attr("fill", "none")
// .call(g => g.append("circle")
// .attr("stroke", "#000")
// .attr("stroke-opacity", 0.5)
// .attr("r", y))
// .call(g => g.append("text")
// .attr("y", d => -y(d))
// .attr("dy", "0.35em")
// .attr("stroke", "#fff")
// .attr("stroke-width", 5)
// .text(y.tickFormat(5, "s"))
// .clone(true)
// .attr("fill", "#000")
// .attr("stroke", "none")))

// const arc = d3.arc()
// .innerRadius(d => y(d[0]))
// .outerRadius(d => y(d[1]))
// .startAngle(d => x(d.data.getHours()))
// .endAngle(d => x(d.data.getHours()) + x.bandwidth())
// .padAngle(0.01)
// .padRadius(innerRadius);

// svg.append("g")
// .call(yAxis);

// return svg.node();
// }
Insert cell
// This scale maintains area proportionality of radial bars
y = d3.scaleBand()
.domain([1,2,3])
//.domain([...new Set(pge_src.map(d => daysIntoYear(d.date)))])
.range([0, 250, 500])

Insert cell
y.ticks
Insert cell
pge_src.map(d => d3.timeDay(d.date).getDate())
Insert cell
function daysIntoYear(date){
return (Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()) - Date.UTC(date.getFullYear(), 0, 0)) / 24 / 60 / 60 / 1000;
}
Insert cell
function daysIntoYear2(date){
return (Date.UTC(2019, date.month, date.day-1) - Date.UTC(2019, 0, 0)) / 24 / 60 / 60 / 1000;
}
Insert cell
[...new Set(pge_src.map(d => daysIntoYear(d.date)))]
Insert cell
circularHeatChart
Insert cell
circularHeatChart = {
let margin = {top: 20, right: 20, bottom: 20, left: 20}
let innerRadius = 50
let numSegments = 24
let segmentHeight = 20
let domain = null
let range = ["white", "red"]
let accessor = function(d) {return d;}
let radialLabels = [...new Set(pge_src.map(d => d.date.getHours()))];
let segmentLabels = [...new Set(pge_src.map(d => daysIntoYear(d.date)))];

function chart(selection) {
selection.each(function(data) {
var svg = d3.select(this);

var offset = innerRadius + Math.ceil(data.length / numSegments) * segmentHeight;
const g = svg.append("g")
.classed("circular-heat", true)
.attr("transform", "translate(" + parseInt(margin.left + offset) + "," + parseInt(margin.top + offset) + ")");

var autoDomain = false;
if (domain === null) {
domain = d3.extent(data, accessor);
autoDomain = true;
}
var color = d3.scale.linear().domain(domain).range(range);
if(autoDomain)
domain = null;

g.selectAll("path").data(data)
.enter().append("path")
.attr("d", d3.svg.arc().innerRadius(ir).outerRadius(or).startAngle(sa).endAngle(ea))
.attr("fill", function(d) {return color(accessor(d));});


// Unique id so that the text path defs are unique - is there a better way to do this?
var id = d3.selectAll(".circular-heat")[0].length;

//Radial labels
var lsa = 0.01; //Label start angle
var labels = svg.append("g")
.classed("labels", true)
.classed("radial", true)
.attr("transform", "translate(" + parseInt(margin.left + offset) + "," + parseInt(margin.top + offset) + ")");

labels.selectAll("def")
.data(radialLabels).enter()
.append("def")
.append("path")
.attr("id", function(d, i) {return "radial-label-path-"+id+"-"+i;})
.attr("d", function(d, i) {
var r = innerRadius + ((i + 0.2) * segmentHeight);
return "m" + r * Math.sin(lsa) + " -" + r * Math.cos(lsa) +
" a" + r + " " + r + " 0 1 1 -1 0";
});

labels.selectAll("text")
.data(radialLabels).enter()
.append("text")
.append("textPath")
.attr("xlink:href", function(d, i) {return "#radial-label-path-"+id+"-"+i;})
.style("font-size", 0.6 * segmentHeight + 'px')
.text(function(d) {return d;});

//Segment labels
var segmentLabelOffset = 2;
var r = innerRadius + Math.ceil(data.length / numSegments) * segmentHeight + segmentLabelOffset;
labels = svg.append("g")
.classed("labels", true)
.classed("segment", true)
.attr("transform", "translate(" + parseInt(margin.left + offset) + "," + parseInt(margin.top + offset) + ")");

labels.append("def")
.append("path")
.attr("id", "segment-label-path-"+id)
.attr("d", "m0 -" + r + " a" + r + " " + r + " 0 1 1 -1 0");

labels.selectAll("text")
.data(segmentLabels).enter()
.append("text")
.append("textPath")
.attr("xlink:href", "#segment-label-path-"+id)
.attr("startOffset", function(d, i) {return i * 100 / numSegments + "%";})
.text(function(d) {return d;});
});

return svg;
}

/* Arc functions */
let ir = function(d, i) {
return innerRadius + Math.floor(i/numSegments) * segmentHeight;
}
let or = function(d, i) {
return innerRadius + segmentHeight + Math.floor(i/numSegments) * segmentHeight;
}
let sa = function(d, i) {
return (i * 2 * Math.PI) / numSegments;
}
let ea = function(d, i) {
return ((i + 1) * 2 * Math.PI) / numSegments;
}

/* Configuration getters/setters */
chart.margin = function(_) {
if (!arguments.length) return margin;
margin = _;
return chart;
};

chart.innerRadius = function(_) {
if (!arguments.length) return innerRadius;
innerRadius = _;
return chart;
};

chart.numSegments = function(_) {
if (!arguments.length) return numSegments;
numSegments = _;
return chart;
};

chart.segmentHeight = function(_) {
if (!arguments.length) return segmentHeight;
segmentHeight = _;
return chart;
};

chart.domain = function(_) {
if (!arguments.length) return domain;
domain = _;
return chart;
};

chart.range = function(_) {
if (!arguments.length) return range;
range = _;
return chart;
};

chart.radialLabels = function(_) {
if (!arguments.length) return radialLabels;
if (_ == null) _ = [];
radialLabels = _;
return chart;
};

chart.segmentLabels = function(_) {
if (!arguments.length) return segmentLabels;
if (_ == null) _ = [];
segmentLabels = _;
return chart;
};

chart.accessor = function(_) {
if (!arguments.length) return accessor;
accessor = _;
return chart;
};

return chart;
}
Insert cell
width = 1000
Insert cell
height = 1000
Insert cell
# Chart code
Insert cell
Insert cell
Plot.plot({
width: 1000,
height: 1500,
padding: 0,
x: {axis: "top", tickFormat: formatHour, round: false},
y: {tickFormat: formatDay, round: false},
color: {type: "diverging", scheme: "BuRd"},
marks: [
Plot.cell(
pge_src,
{
x: d => d.date.getHours(),
y: d => d3.timeDay(d.date),
fill: "usage",
inset: 0.5,
title: d => `${formatDate(d.date)}\n${formatUsage(d.usage)} kW`
}
)
]
})
Insert cell
pge_src
Insert cell
test2.SIN[0]
Insert cell
function labelX(angle, radius) {
// change to clockwise
let a = 360 - angle
// start from 12 o'clock
a = a + 180;
return radius * Math.sin(a * radians)
}

Insert cell
function labelY(angle, radius) {
// change to clockwise
let a = 360 - angle
// start from 12 o'clock
a = a + 180;
return radius * Math.cos(a * radians)
}
Insert cell
radians = 0.0174532925
Insert cell
Insert cell
import {data as pge_src, formatUsage, formatDate, formatDay, formatHour, dateExtent} from "@mbostock/electric-usage-2019"
Insert cell
import {arcLabels} from "@tomshanley/spiral-heatmap-northern-hemisphere-sea-ice-extent-1978-to-2"
Insert cell
arcLabels
Insert cell
test1 = FileAttachment("ssdata.csv").csv()
Insert cell
test2 = (FileAttachment("sin@2.json").json())
Insert cell
sin = (FileAttachment("sin@3.json").json())
Insert cell
atl = (FileAttachment("atl.json").json())
Insert cell
lhr = (FileAttachment("lhr.json").json())
Insert cell
lax = (FileAttachment("lax.json").json())
Insert cell
test3 = { const data = FileAttachment("lhr.json").json()
return data; }
Insert cell
diameter = Math.min(screen.width, screen.height) * 0.8;
Insert cell
// import {RingChart} from "@analyzer2004/ringchart"
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