Public
Edited
Dec 19
Importers
1 star
Insert cell
# Tile Map
Insert cell
filteredTiles
Insert cell
viewof minArea = Range([0.001, 0.01], { step: 0.001 })
Insert cell
linePoints
Insert cell
projection = d3.geoMercator()
.fitSize([width, height], engWales_simplified);
Insert cell
{
var projection = d3.geoMercator()
.fitSize([500, 300], engWales_simplified);

var path = d3.geoPath()
.projection(projection);

var path2 = smooth(d3.curveBasisClosed, projection);
let tile = filteredTiles[0].geometry.coordinates[0];

return engWales_simplified
return topojson.merge(engWales_simplified, engWales_simplified.coordinates)

return polygonClip(...engWales_simplified.coordinates, tile)
}


Insert cell
import {polygonClip} from "@d3/polygonclip"
Insert cell
numTiles = 10.8
Insert cell
d3.polygonCentroid(...filteredTiles[0]['geometry']['coordinates'])
Insert cell
Prob = tileSpeciesProb(Species)
Insert cell
radiusScale =
// d3.scaleSqrt()
d3.scaleLinear()
// .exponent(0.5)
.domain([0,1])
.range([0,3])
Insert cell
colorScale = d3.scaleSequential(d3.interpolateMagma)
.domain([-50,height]);
Insert cell
filteredTiles.map(d => d.geometry)
Insert cell
tileSpeciesProb('Brown / sea trout')
Insert cell
Insert cell
// import { FileAttachment } from "@observablehq/stdlib";
Insert cell
{
const file = FileAttachment("brown-trout-coe.json");
file.write(JSON.stringify(brownTroutCoE))

}
Insert cell
Plot.plot({
marks: [
Plot.rectY(brownTroutCoE, Plot.binX({y: "count"}, {x: "prob"})),
Plot.ruleY([0])
]
})
Insert cell
viewof surveyThreshold = Range([3, 10], { step: 1 })
Insert cell
Insert cell
Insert cell
{
let Prob = tileSpeciesProb("Grayling");

Prob = Prob.filter(d => d.prob >= 0.2 && d.surveys >= 4);
let graylingTiles = Prob.map(d => d.tile);

return filteredTiles.filter(d => graylingTiles.includes(d.tile))
}
Insert cell
linePoints[0].coords.map(d => projection(d))
Insert cell
import { geoCurvePath as smooth } from "@d3/context-to-curve"
Insert cell
cover.geojson(geometryCollection['geometry'], { min_zoom: 8, max_zoom: 8})
Insert cell
engwalestopo1 = FileAttachment("engWalesTopo.json").json()
Insert cell
engwalestopo11 = FileAttachment("engWalesTopo@1.json").json()
Insert cell
tiles = {
// Zoom is used to control the number of tiles
// Higher zoom generates more tiles
const zoom = numTiles

// We generate tiles for each feature geometry using cover.tiles
const limits = { min_zoom: zoom, max_zoom: zoom }
// const tiles = cover.geojson(geometryCollection['geometry'], { min_zoom: zoom, max_zoom: zoom})
const tiles = cover.geojson(engWales_Geo, { min_zoom: zoom, max_zoom: zoom})
return tiles
}
Insert cell
linePoints = {

let results = [];
filteredTiles.map(function(d){
let coords = d.geometry.coordinates[0]
let distance = ((coords[1][0] - coords[0][0]) / 2)

let p0x = coords[0][0]
let p0y = coords[0][1] + distance
let p1x = coords[3][0]
let p1y = coords[3][1] + distance
results.push({
'tile': d.tile,
'coords': [[p0x,p0y],[p1x,p1y]]
});
}) ;

return results
}

Insert cell
Insert cell
tileSpeciesProb = function(species){
// tileSiteCatches[0].sites[0].surveys.map(d => d.SPECIES.includes("Brown / sea trout"))

let tileSpeciesProb = [];
let speciesPresent = tileSiteCatches.map(function(d){
let a = d.sites.map(function(e){
return e.surveys.map(f => f.SPECIES.includes(species))
});
let speciesBool = a.flat();

tileSpeciesProb.push({
tile: d.tile,
prob: speciesBool.filter(Boolean).length / speciesBool.length,
surveys: +speciesBool.length
});
});

// let speciesProb = speciesPresent.map(d => d.filter(Boolean).length / d.length)
return tileSpeciesProb
}
Insert cell
tileSurveysObj = Object.entries(tileSurveys).map(([key, value]) => ({
tile: key,
surveys: value,
}));
Insert cell
{
// Count the number of tiles with <5 surveys
const tilesWithLessThan5Surveys = tileSurveysObj.reduce((count, tile) => {
if (tile.surveys < 5) {
return count + 1;
}
return count;
}, 0);

return tilesWithLessThan5Surveys / tileSurveysObj.length * 100

}
Insert cell
tileSurveys = {
let tileSurveys = {};
tileSiteCatches.map(function(d){
let surveys = 0;
d.sites.map(function(e){
surveys += e.surveys.length
})
return tileSurveys[d.tile] = surveys
});

return tileSurveys
}
Insert cell
Plot.plot({
marks: [
Plot.rectY(tileSurveysObj, Plot.binX({y: "count"}, {x: "surveys", domain: [0,100], thresholds: 20})),
Plot.ruleY([0])
]
})

Insert cell
{
const pre2010tiles = [];

tileSiteCatches.map(function(tile){
//each tile
const pre2010sites = [];
const siteList = tile.sites.filter(function(site){
//each site
const pre2010surveyList = [];
site.surveys.map(function(surveys){
//each survey
if (surveys.YEAR <= 2010) {
pre2010surveyList.push(surveys)
}
});

if (pre2010surveyList.length == 0) return;
const pre2010surveys = {
site: site.site,
surveys: pre2010surveyList
};
return pre2010surveys;
});

// return pre2010surveys

pre2010tiles.push({
tile: tile.tile,
sites: siteList
})
});

return pre2010tiles
}

Insert cell
catchDataYearFilter(2001,2001)
Insert cell
catchDataYearFilter = function(fromYear,toYear)
{
const filteredData = tileSiteCatches.filter((tileData) => {
const filteredSites = tileData.sites.filter((site) => {
const filteredSurveys = site.surveys.filter((survey) => {
// return parseInt(survey.YEAR) >= fromYear && parseInt(survey.YEAR) <= toYear;
return parseInt(survey.YEAR) < 2002
});


if (filteredSurveys.length == 0){
return false;
}

return {
site: site.site,
surveys: filteredSurveys
};
});


console.log(filteredSites, filteredSites.length)


if (filteredSites.length == 0){
return false
};
return {
tile: tileData.tile,
sites: filteredSites
};
});
return filteredData
}
Insert cell
tileSiteCatches = {
let filledTiles = tileSites.filter(d => d.sites.length >= 1);
let data = [];
filledTiles.map(function(t){
let tileSiteSurveys=[];
t.sites.map(function(d){
let surveys=[];

if (catchSites.includes(d)){
catches2.map(function(e){
d == e.SITE_ID?
surveys.push(e)
: null
});
let siteSurveys = {
site: d,
surveys: surveys
}
tileSiteSurveys.push(siteSurveys)
};
});

data.push({
tile: t.tile,
sites: tileSiteSurveys
});

});
return data;
};
Insert cell
catches2
Insert cell
// Copyright 2021, Observable Inc.
// Released under the ISC license.
// https://observablehq.com/@d3/color-legend
function Legend(color, {
title,
tickSize = 6,
width = 320,
height = 44 + tickSize,
marginTop = 18,
marginRight = 0,
marginBottom = 16 + tickSize,
marginLeft = 0,
ticks = width / 64,
tickFormat,
tickValues
} = {}) {

function ramp(color, n = 256) {
const canvas = document.createElement("canvas");
canvas.width = n;
canvas.height = 1;
const context = canvas.getContext("2d");
for (let i = 0; i < n; ++i) {
context.fillStyle = color(i / (n - 1));
context.fillRect(i, 0, 1, 1);
}
return canvas;
}

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.style("overflow", "visible")
.style("display", "block");

let tickAdjust = g => g.selectAll(".tick line").attr("y1", marginTop + marginBottom - height);
let x;

// Continuous
if (color.interpolate) {
const n = Math.min(color.domain().length, color.range().length);

x = color.copy().rangeRound(d3.quantize(d3.interpolate(marginLeft, width - marginRight), n));

svg.append("image")
.attr("x", marginLeft)
.attr("y", marginTop)
.attr("width", width - marginLeft - marginRight)
.attr("height", height - marginTop - marginBottom)
.attr("preserveAspectRatio", "none")
.attr("xlink:href", ramp(color.copy().domain(d3.quantize(d3.interpolate(0, 1), n))).toDataURL());
}

// Sequential
else if (color.interpolator) {
x = Object.assign(color.copy()
.interpolator(d3.interpolateRound(marginLeft, width - marginRight)),
{range() { return [marginLeft, width - marginRight]; }});

svg.append("image")
.attr("x", marginLeft)
.attr("y", marginTop)
.attr("width", width - marginLeft - marginRight)
.attr("height", height - marginTop - marginBottom)
.attr("preserveAspectRatio", "none")
.attr("xlink:href", ramp(color.interpolator()).toDataURL());

// scaleSequentialQuantile doesn’t implement ticks or tickFormat.
if (!x.ticks) {
if (tickValues === undefined) {
const n = Math.round(ticks + 1);
tickValues = d3.range(n).map(i => d3.quantile(color.domain(), i / (n - 1)));
}
if (typeof tickFormat !== "function") {
tickFormat = d3.format(tickFormat === undefined ? ",f" : tickFormat);
}
}
}

// Threshold
else if (color.invertExtent) {
const thresholds
= color.thresholds ? color.thresholds() // scaleQuantize
: color.quantiles ? color.quantiles() // scaleQuantile
: color.domain(); // scaleThreshold

const thresholdFormat
= tickFormat === undefined ? d => d
: typeof tickFormat === "string" ? d3.format(tickFormat)
: tickFormat;

x = d3.scaleLinear()
.domain([-1, color.range().length - 1])
.rangeRound([marginLeft, width - marginRight]);

svg.append("g")
.selectAll("rect")
.data(color.range())
.join("rect")
.attr("x", (d, i) => x(i - 1))
.attr("y", marginTop)
.attr("width", (d, i) => x(i) - x(i - 1))
.attr("height", height - marginTop - marginBottom)
.attr("fill", d => d);

tickValues = d3.range(thresholds.length);
tickFormat = i => thresholdFormat(thresholds[i], i);
}

// Ordinal
else {
x = d3.scaleBand()
.domain(color.domain())
.rangeRound([marginLeft, width - marginRight]);

svg.append("g")
.selectAll("rect")
.data(color.domain())
.join("rect")
.attr("x", x)
.attr("y", marginTop)
.attr("width", Math.max(0, x.bandwidth() - 1))
.attr("height", height - marginTop - marginBottom)
.attr("fill", color);

tickAdjust = () => {};
}

svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(d3.axisBottom(x)
.ticks(ticks, typeof tickFormat === "string" ? tickFormat : undefined)
.tickFormat(typeof tickFormat === "function" ? tickFormat : undefined)
.tickSize(tickSize)
.tickValues(tickValues))
.call(tickAdjust)
.call(g => g.select(".domain").remove())
.call(g => g.append("text")
.attr("x", marginLeft)
.attr("y", marginTop + marginBottom - height - 6)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.attr("class", "title")
.text(title));

return svg.node();
}
Insert cell
// neighbouringPoints = function(tile){
// tile = tile['geometry']['coordinates'];
// let sideLength = Math.sqrt(d3.polygonArea(...tile))
// let centroid = d3.polygonCentroid(...tile)
// let buffer = 0.7
// // return tile
// let x0 = centroid[0] - sideLength * buffer;
// let x1 = centroid[0] + sideLength * buffer;
// let y0 = centroid[1] - sideLength * buffer;
// let y1 = centroid[1] + sideLength * buffer;

// // return y0,y1

// return sitePositions.filter(
// d => d.COORDS[0] > x0 &&
// d.COORDS[0] < x1 &&
// d.COORDS[1] > y0 &&
// d.COORDS[1] < y1
// )
// }
Insert cell
// tileFeatures=memoize(tileFeaturesFn)()
Insert cell
tileFeatures = {
let tileFeatures = tiles['features'];
for (let i=0; i < tileFeatures.length; i++){
tileFeatures[i].tile = i;
tileFeatures[i].geometry.centroid = d3.polygonCentroid(tileFeatures[i].geometry.coordinates[0]);
}
return tileFeatures
};
Insert cell
tileSites.filter(d => d.sites.length >= 1)
Insert cell
filteredTiles = {
let filledTiles = tileSites.filter(d => d.sites.length >= 1).map(d => d.tile)
return tileFeatures.filter(d => filledTiles.includes(d.tile))
}
Insert cell
// tileSites2 = {
// let results = [];
// const tileList = tiles['features']

// for (let i=0; i < tileList.length; i++){
// results.push({'tile':i, 'sites': []});

// let contains = geoContainsCache(tileList[i])
// let neighbours = neighbouringPoints(tileList[i])

// for (let j=0; j < neighbours.length; j++){
// let inTile = contains(neighbours[j].COORDS);
// if (inTile ? results[i].sites.push(neighbours[j].SITE_ID) : !inTile) continue;
// };
// };

// return results
// }
Insert cell
// tileSites = FileAttachment("tileSites.json").json()
Insert cell
sites.filter(d => d.SITE_ID == '13651')
Insert cell
tileSites = {
let results = [];
sites.map(function(d){
let tile = findTile(d)
results.push({tile: tile, site:d.SITE_ID})
});


let map = d3.group(results, d => d.tile)
const arr = Array.from(map, ([key, value]) => {
return {tile:key,value};
});

let results2 = [];
arr.map(function(d){
let tile = d.tile

let siteArr = [];
d.value.map(function(e){
siteArr.push(e.site)
})

if (tile !== undefined) {
results2.push({tile: tile, sites: siteArr})
}
});

return results2

}

Insert cell
sitePositions = {
let sitePositions = [];
recentSites.map( d =>
sitePositions.push({
SITE_ID: d.SITE_ID,
COORDS: [+d.Long, +d.Lat]
})
)
return sitePositions
}

Insert cell
recentSites = {
const regex = /\d{2}\/\d{2}\/(\d{4})/;
return sites.filter(function(d){
const surveyYear = regex.exec(d.LAST_SURVEY)[1]
// return +surveyYear > 2000
return +surveyYear >= 2000 && catchSites.includes(d.SITE_ID)
})
}
Insert cell
sites = FileAttachment("FW_Fish_Sites_LatLong@5.csv").csv()
Insert cell
import {geojson} from "@syaleni/topojson-vs-geojson"
Insert cell
d3 = require('d3')
Insert cell
cover = import('https://cdn.skypack.dev/tile-cover@3.0.1?min')
Insert cell
engwalesgeo = FileAttachment("engWalesGeo.json").json()
Insert cell
engwalestopo = FileAttachment("engWalesTopo@1.json").json()
Insert cell
engWales_Geo = {
return topojson.merge(engwalestopo, Object.values(engwalestopo.objects))
}
Insert cell
engWales_simplified = {
let engWales_simplified = topojson.presimplify(engwalestopo);
let min_weight = topojson.quantile(engWales_simplified, minArea);
engWales_simplified = topojson.simplify(engWales_simplified, min_weight);
return topojson.merge(engWales_simplified, Object.values(engWales_simplified.objects))
}
Insert cell
topojson = require("https://unpkg.com/topojson@3")
Insert cell
geometryCollection.geometry
Insert cell
engWales_Geo
Insert cell
geometryCollection = ({
type: "GeometryCollection",
geometry: {
type: 'MultiPolygon',
coordinates: engWales_Geo.coordinates
}
})
Insert cell
test = FileAttachment("test.json").json()
Insert cell
engWales_simplified
Insert cell
engwalestopo
Insert cell
[...new Set(catches.map(d => d.SPECIES_NAME))]
Insert cell
catchSites = [...new Set(catches.map(d => d.SITE_ID))]
Insert cell
speciesList = [...new Set(catches.map(d => d.SPECIES_NAME.split(';')).flat())]

Insert cell
catches2 = {
let catches2 = [];
catches.map(function(d){
let bag = {
SITE_ID: d.SITE_ID,
SITE_NAME: d.TOP_TIER_SITE,
SURVEY_ID: d.SURVEY_ID,
YEAR: d.EVENT_DATE_YEAR,
SPECIES: d.SPECIES_NAME.split(';')
}

catches2.push(bag)
});

return catches2

}
Insert cell
[sites[1].Long,sites[1].Lat]
Insert cell
findTile([sites[1].Long,sites[1].Lat])
Insert cell
sites
Insert cell
tiles.features[0].geometry.coordinates[0][2]
Insert cell
findTile(-2,55)
Insert cell
tiles.features[0].geometry.coordinates[0][2].map(d => Math.round((d)*1000)/1000)
Insert cell
corners = tiles.features.map((d, i) => [
...d.geometry.coordinates[0][2].map(d => Math.round((d)*1000)/1000),
i
]);
Insert cell
I.get(x0)
Insert cell
x2 = d3.sort(d3.group(corners, (d) => +d[0]).keys())
Insert cell
margin = ({ top: 10, right: 10, bottom: 10, left: 10 })
Insert cell
tooltipTemplate = html`<div class="tooltip tooltip-${tooltipId}">
${tooltipStyles}
<div class="tooltip-contents"></div>
</div>`
Insert cell
height = 400
Insert cell
function setStyle(selection) {
selection.attr("fill", "magenta");
}
Insert cell
function resetStyle(selection) {
selection.attr("fill", "#333");
}
Insert cell
MOUSE_POS_OFFSET = 8
Insert cell
tooltipId = DOM.uid().id
Insert cell
tooltipStyles = htl.html`<style>
/* modify these styles to however you see fit */
div.tooltip-${tooltipId} {
box-sizing: border-box;
position: absolute;
display: none;
top: 0;
left: -100000000px;
padding: 8px 12px;
font-family: sans-serif;
font-size: 12px;
color: #333;
background-color: #fff;
border: 1px solid #333;
border-radius: 4px;
pointer-events: none;
z-index: 1;
}
div.tooltip-${tooltipId} p {
margin: 0;
}
</style>`
Insert cell
tooltip = (selectionGroup, tooltipDiv) => {
selectionGroup.each(function () {
d3.select(this)
.on("mouseover.tooltip", handleMouseover)
.on("mousemove.tooltip", handleMousemove)
.on("mouseleave.tooltip", handleMouseleave);
});

function handleMouseover() {
// show/reveal the tooltip, set its contents,
// style the element being hovered on
showTooltip();
setContents(d3.select(this).datum(), tooltipDiv);
setStyle(d3.select(this));
}

function handleMousemove(event) {
// update the tooltip's position
const [mouseX, mouseY] = d3.pointer(event, this);
// add the left & top margin values to account for the SVG g element transform
setPosition(mouseX + margin.left, mouseY + margin.top);
}

function handleMouseleave() {
// do things like hide the tooltip
// reset the style of the element being hovered on
hideTooltip();
resetStyle(d3.select(this));
}

function showTooltip() {
tooltipDiv.style("display", "block");
}

function hideTooltip() {
tooltipDiv.style("display", "none");
}

function setPosition(mouseX, mouseY) {
tooltipDiv
.style(
"top",
mouseY < height / 2 ? `${mouseY + MOUSE_POS_OFFSET}px` : "initial"
)
.style(
"right",
mouseX > width / 2
? `${width - mouseX + MOUSE_POS_OFFSET}px`
: "initial"
)
.style(
"bottom",
mouseY > height / 2
? `${height - mouseY + MOUSE_POS_OFFSET}px`
: "initial"
)
.style(
"left",
mouseX < width / 2 ? `${mouseX + MOUSE_POS_OFFSET}px` : "initial"
);
}
}
Insert cell
function setContents(datum, tooltipDiv) {
// customize this function to set the tooltip's contents however you see fit
tooltipDiv
.selectAll("p")
.data(Object.entries(datum))
.join("p")
.filter(([key, value]) => value !== null && value !== undefined)
.html(
([key, value]) =>
`<strong>${key}</strong>: ${
typeof value === "object" ? value.toLocaleString("en-US") : value
}`
);
}
Insert cell
corners.map(d => d[0]).includes(-1.866432229648040)
Insert cell
-1.8664322296480407
Insert cell
corners.map((v) => v[0][2])
Insert cell
I = d3.rollup(
corners,
(v) => v[0][2],
(d) => +d[0],
(d) => +d[1]
);
Insert cell
Insert cell
site = [-3.551356790934276, 51.9259578835074]
Insert cell
findTile({'Long': site[0], 'Lat': site[1]})
Insert cell
I.get(x0)?.get(y0)
Insert cell
x = d3.sort(d3.group(corners, (d) => +d[0]).keys())
Insert cell
y = d3.sort(d3.group(corners, (d) => +d[1]).keys())
Insert cell
x0 = x[d3.bisect(x, site[0])]
Insert cell
d3.bisect(y, 51.79659578835074)
Insert cell
y0 = y[d3.bisect(y, site[1])]
Insert cell
findTile = {
const corners = tiles.features.map((d, i) => [
...d.geometry.coordinates[0][2].map(e => Math.round((e)*1000)/1000),
i
]);
const x = d3.sort(d3.group(corners, (d) => +d[0]).keys());
const y = d3.sort(d3.group(corners, (d) => +d[1]).keys());
const I = d3.rollup(
corners,
(v) => v[0][2],
(d) => +d[0],
(d) => +d[1]
);
return ({ Long, Lat }) => {
const x0 = x[d3.bisect(x, +Long)];
const y0 = y[d3.bisect(y, +Lat)];
const i = I.get(x0)?.get(y0);

return i

// Here we could return i, but it's safer to double-check that the polygon
// we've reached does indeed contain the point. We use d3.polygonContains
// because the grid is planar not spherical.
// return i !== undefined
// ? d3.polygonContains(tiles.features[i].geometry.coordinates[0], [
// +Long,
// +Lat
// ])
// ? i
// : undefined
// : undefined;
};
}
Insert cell
catches = FileAttachment("FW_Fish_Counts_Filtered@6.csv").csv()
Insert cell
d3.min(catches.map(d => d.EVENT_DATE_YEAR))
Insert cell
function legend({
color,
title,
tickSize = 6,
width = 36 + tickSize,
height = 320,
marginTop = 20,
marginRight = 10 + tickSize,
marginBottom = 20,
marginLeft = 5,
ticks = height / 64,
tickFormat,
tickValues
} = {}) {

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.style("overflow", "visible")
.style("display", "block");

let tickAdjust = g => g.selectAll(".tick line").attr("x1", marginLeft - width + marginRight);
let x;

// Continuous
if (color.interpolate) {
const n = Math.min(color.domain().length, color.range().length);

x = color.copy().rangeRound(d3.quantize(d3.interpolate(height - marginBottom, marginTop), n));

svg.append("image")
.attr("x", marginLeft)
.attr("y", marginTop)
.attr("width", width - marginLeft - marginRight)
.attr("height", height - marginTop - marginBottom)
.attr("preserveAspectRatio", "none")
.attr("xlink:href", ramp(color.copy().domain(d3.quantize(d3.interpolate(0, 1), n))).toDataURL());
}

// Sequential
else if (color.interpolator) {
x = Object.assign(color.copy()
.interpolator(d3.interpolateRound(height - marginBottom, marginTop)),
{range() { return [height - marginBottom, marginTop]; }});

svg.append("image")
.attr("x", marginLeft)
.attr("y", marginTop)
.attr("width", width - marginLeft - marginRight)
.attr("height", height - marginTop - marginBottom)
.attr("preserveAspectRatio", "none")
.attr("xlink:href", ramp(color.interpolator()).toDataURL());

// scaleSequentialQuantile doesn’t implement ticks or tickFormat.
if (!x.ticks) {
if (tickValues === undefined) {
const n = Math.round(ticks + 1);
tickValues = d3.range(n).map(i => d3.quantile(color.domain(), i / (n - 1)));
}
if (typeof tickFormat !== "function") {
tickFormat = d3.format(tickFormat === undefined ? ",f" : tickFormat);
}
}
}

// Threshold
else if (color.invertExtent) {
const thresholds
= color.thresholds ? color.thresholds() // scaleQuantize
: color.quantiles ? color.quantiles() // scaleQuantile
: color.domain(); // scaleThreshold

const thresholdFormat
= tickFormat === undefined ? d => d
: typeof tickFormat === "string" ? d3.format(tickFormat)
: tickFormat;

x = d3.scaleLinear()
.domain([-1, color.range().length - 1])
.rangeRound([height - marginBottom, marginTop]);

svg.append("g")
.selectAll("rect")
.data(color.range())
.join("rect")
.attr("y", (d, i) => x(i))
.attr("x", marginLeft)
.attr("height", (d, i) => x(i - 1) - x(i))
.attr("width", width - marginRight - marginLeft)
.attr("fill", d => d);

tickValues = d3.range(thresholds.length);
tickFormat = i => thresholdFormat(thresholds[i], i);
}

// Ordinal
else {
x = d3.scaleBand()
.domain(color.domain())
.rangeRound([height - marginBottom, marginTop]);

svg.append("g")
.selectAll("rect")
.data(color.domain())
.join("rect")
.attr("y", x)
.attr("x", marginLeft)
.attr("height", Math.max(0, x.bandwidth() - 1))
.attr("width", width - marginLeft - marginRight)
.attr("fill", color);

tickAdjust = () => {};
}

svg.append("g")
.attr("transform", `translate(${width - marginRight},0)`)
.call(d3.axisRight(x)
.ticks(ticks, typeof tickFormat === "string" ? tickFormat : undefined)
.tickFormat(typeof tickFormat === "function" ? tickFormat : undefined)
.tickSize(tickSize)
.tickValues(tickValues))
.call(tickAdjust)
.call(g => g.select(".domain").remove())
.call(g => g.append("text")
.attr("x", 0)
.attr("y", 0)
.attr("fill", "currentColor")
.attr("text-anchor", "middle")
.attr("font-weight", "bold")
.attr("class", "title")
.text(title));

return svg.node();
}

Insert cell
function ramp(color, n = 256) {
const canvas = DOM.canvas(1, n);
const context = canvas.getContext("2d");
for (let i = 0; i < n; ++i) {
context.fillStyle = color(i / (n - 1));
context.fillRect(0, n-i, 1, 1);
}
return canvas;
}
Insert cell
import { Range } from "@observablehq/inputs"
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