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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more