Public
Edited
Oct 21, 2023
Fork of Slope chart
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
mutable siteName = "Duluth"
Insert cell
slopeChart = function(fullSize, data, leftCol, rightCol, valueCol, tooltipContents, lineClick) {
if (!fullSize && (typeof tooltipContents === "function" || typeof lineClick === "function")) {
throw "thumbnail chart does not support tooltips or line clicks";
}
// Specify the chart’s dimensions.
const width = fullSize ? 928 : 100;
const height = fullSize ? 400 : 50;
const marginTop = fullSize ? 40 : 0;
const marginRight = fullSize ? 50 : 0;
const marginBottom = fullSize ? 10 : 0;
const marginLeft = fullSize ? 50 : 0;
const padding = fullSize ? 3 : 1;

// tooltip setup
const container = html`<div class="slopechart_class">
<style>
/*
* Be sure to set the tooltip's DOM element's styles!
*/
div.slopechart_class > div.tooltip {
position: fixed;
display: none;
padding: 12px 6px;
background: #fff;
border: 1px solid #333;
pointer-events: none;
}
div.slopechart_class > svg {
max-width: 100%;
height: auto;
font: 10px sans-serif;
}
</style>
</div>`;
const svg = d3
.select(container)
.append("svg")
.attr("viewBox", `0 0 ${width} ${height}`);

if (!fullSize) svg.attr("width", `${width}`).attr("height", `${height}`);

const div = d3
.select(container)
.append("div")
.classed("tooltip", true);

// create a new tooltip,
// passing it a selection for the tooltip's DOM element.
const tooltip = new Tooltip(div);

// slope chart customization: event handlers
function lineMouseOver(event, d) {
// Optional highlight effects on target
d3.select(event.target)
.transition()
.attr('stroke-width', lineSelectedWidth);
if (lineClick !== undefined) {
// Optional cursor change on target
d3.select(event.target).style("cursor", "pointer");
}
if (tooltipContents !== undefined) {
console.log("reveal the tooltip.")
// pass the current datum (if desired),
// and a render function that returns an HTML string (required)
tooltip.display(d, tooltipContents);
}
}

function lineMouseMove(event, d) {
if (tooltipContents !== undefined) {
// move the tooltip,
// passing the native browser event
console.log("move the tooltip.")
tooltip.move(event);
}
};
function lineMouseOut(event, d) {
// highlight removed
d3.select(event.target)
.transition()
.attr('stroke-width', lineWidth);

if (lineClick !== undefined) {
// Cursor change removed
d3.select(event.target).style("cursor", "default");
}

if (tooltipContents !== undefined) {
// hide the tooltip
console.log("hide the tooltip.")
tooltip.hide();
}
}
// Prepare the positional scales.
const x = d3.scalePoint()
.domain([0, 1])
.range([marginLeft, width - marginRight])
.padding(0.5);

const y = d3.scaleLinear()
.domain(d3.extent(data.flatMap(d => [d[leftCol], d[rightCol]])))
.range([height - marginBottom, marginTop]);

const line = d3.line()
.x((d, i) => x(i))
.y(y);

const formatNumber = y.tickFormat(100);
// Append the x axis, if showing the fullsize version of the chart
if (fullSize) {
svg.append("g")
.attr("text-anchor", "middle")
.selectAll("g")
.data([0, 1])
.join("g")
.attr("transform", (i) => `translate(${x(i)},20)`)
.call(g => g.append("text").text((i) => i ? rightCol : leftCol))
.call(g => g.append("line").attr("y1", 3).attr("y2", 9).attr("stroke", "currentColor"));
}
// Create a line for each country.
svg.append("g")
.attr("fill", "none")
.attr("stroke", "currentColor")
.selectAll("path")
.data(data)
.join("path")
.attr("d", (d) => line([d[leftCol], d[rightCol]]))
.attr("stroke", (d) => d[leftCol] > d[rightCol] ? "blue" : "red")
.attr('stroke-width', lineWidth)
.on('mouseover', lineMouseOver)
.on('mouseout', lineMouseOut )
.on('mousemove', lineMouseMove )
.on('click', lineClick );

// Create a group of labels for each year.
if (fullSize) {
svg.append("g")
.selectAll("g")
.data([0, 1])
.join("g")
.attr("transform", (i) => `translate(${x(i) + (i ? padding : -padding)},0)`)
.attr("text-anchor", (i) => i ? "start" : "end")
.selectAll("text")
.data((i) => d3.zip(
data.map(i ?
(d) => `${formatNumber(d[rightCol])} ${d[valueCol]}` :
(d) => `${d[valueCol]} ${formatNumber(d[leftCol])}`),
dodge(data.map(d => y(d[i ? rightCol : leftCol])))))
.join("text")
.attr("y", ([, y]) => y)
.attr("dy", "0.35em")
.text(([text]) => text);
//TODO: pass i, text1, text2, use i to decide later if it is left or right, use tspan to style one or the other
}
return container
}
Insert cell
data = FileAttachment("barley.json").json({typed: true})
Insert cell
data.columns = Object.keys(data[0])
Insert cell
groups = d3.rollup(data,
v => d3.sum(v, d => d.yield),
d => d.site, d => d.year
);
Insert cell
Array.from(groups)
Insert cell
groupedData = Array.from(groups).map(([k, v]) => {
let initialValue = [ ['site', k ] ]
let yearValues = Array.from(v)
return Object.fromEntries(
initialValue.concat(yearValues)
)
})
Insert cell
detailedMap = {
let detailedData = d3.rollup(data,
v => d3.sum(v, d => d.yield),
d => d.site, d => d.variety, d => d.year
);
let detailedMap = new Map()
let detailedPivoted = Array.from(detailedData).map(([kSite, vSite]) => {
detailedMap.set(kSite, Array.from(vSite).map(([kVariety, vVariety]) => {
let initialValue = [ ['variety', kVariety ] ]
let yearValues = Array.from(vVariety)
return Object.fromEntries(
initialValue.concat(yearValues)
)
}))
})
return detailedMap
}
Insert cell
Insert cell
lineWidth = 2;
Insert cell
lineSelectedWidth = 4;
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