Public
Edited
Jul 31, 2024
1 fork
Insert cell
Insert cell
height = width * 1.8
Insert cell
neutral = '#000'
Insert cell
background_c = '#fff'
Insert cell
secondary = '#3d5b80'
Insert cell
primary = '#98c0d9'

Insert cell
exampleButton = svgDownloadButton(chart)

Insert cell
function svgDownloadButton(svgnode, filename = 'download.svg') {
const downloadButton = html`<a class="download" download="${filename}" href="#" style="display: flex; align-items: center;">${download_icon.outerHTML} Save ${filename}</a>`;

downloadButton.onclick = function(e) {
var url = (downloadButton.href = URL.createObjectURL(serialize(svgnode)));
setTimeout(function() {
URL.revokeObjectURL(url);
}, 50);
};

return downloadButton;
}

Insert cell
import { serialize, download_icon } from '@kelleyvanevert/little-things'
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
chart = {
const svg = d3.create('svg').attr('id','svg-vis1').attr('class', 'svg-vis').attr("viewBox", [0, 0, width, height]).style('background', background_c)

svg.selectAll('circle').data(plastic_waste_world_sort).enter().append('circle').attr('transform', x => `translate(${width/2},${height*0.65}) rotate(${arc_scaler(x.Entity)-90})`)
.attr("cx", x => stem_scaler(x.mismanaged_pw_capita) + total_pw_scaler(x.pw_total) + leaf_scaler(x.mismanaged_pw_total) + 10)
.attr("cy", 0)
.attr("r", x => total_pw_scaler(x.pw_total))
.attr('stroke-width', 2).attr('stroke', primary)
.style("fill", secondary)
.attr("opacity", 0.4)
const groups = svg.selectAll('g').data(plastic_waste_world_sort).enter().append('g').attr('transform', x => `translate(${width/2},${height*0.65}) rotate(${arc_scaler(x.Entity)-90})`)

groups.append('path').attr('transform', `rotate(${90}, 0, 0)`).attr('d', x => {
const stem = stem_scaler(x.mismanaged_pw_capita)+1
const p_width = leaf_scaler(x.mismanaged_pw_total)+3
// const size = x.mismanaged_
return `M 0 -${inner_radius} C -${0} -${stem-p_width} -${p_width}
-${stem-p_width} -${p_width} -${stem} C -${p_width} -${stem+p_width*1.3} ${p_width} -${stem+p_width*1.3} ${p_width} -${stem} C ${p_width} -${stem-p_width} 0 -${stem-p_width} 0 -${inner_radius}`
}).style("fill",primary).attr('stroke', primary).attr('stroke-width', 0.2).attr('opacity', 1)


groups.append('path').attr('transform', `rotate(${90}, 0, 0)`).attr('d', x => {
let total_pw_len = total_pw_scaler(x.pw_total) ? total_pw_scaler(x.pw_total) : 1
let total_len = stem_scaler(x.mismanaged_pw_capita) + total_pw_len*2 + leaf_scaler(x.mismanaged_pw_total) + 20
return `M 0 -${inner_radius} L 0 -${total_len}`
}).attr('stroke-width', 0.2)
.style("stroke", neutral)
// axis
svg.append("g").attr("transform", `translate(${260/4}, ${50})`)
.call(legend);
svg.append("g").attr("transform", `translate(${260/4}, ${500})`)
.call(legend2);
svg
.append("g")
.attr("class", "axis axis--x")
.attr(
"transform",
`translate(${width/2}, ${height*0.65}) rotate(170, 0, 0)`
)
.call(xAxis);

// .style("stroke-dasharray", ("0.4,0.4"))
// .attr('transform', d => arc_scaler(d.Entity) > 180 ? `rotate(0)` :`rotate(180)`)

// .style("stroke", "gray").style("stroke-dasharray", ("0.4,0.4"))

groups.append('text').attr('x', x => {
let total_pw_len = total_pw_scaler(x.pw_total) ? total_pw_scaler(x.pw_total) : 1
let total_len = stem_scaler(x.mismanaged_pw_capita) + total_pw_len*2 + leaf_scaler(x.mismanaged_pw_total) + 30
return total_len
}).attr('y', 3).text(x => x.Entity).style('font-size', '0.65rem')
.attr('transform', d =>
{
let total_pw_len = total_pw_scaler(d.pw_total) ? total_pw_scaler(d.pw_total) : 1
let total_len = stem_scaler(d.mismanaged_pw_capita) + total_pw_len*2 + leaf_scaler(d.mismanaged_pw_total) + 30

return arc_scaler(d.Entity) > 180 ? `rotate(-180, ${total_len}, 0)` :`rotate(-360,${total_len},0)`
}
)
.attr('text-anchor', d => arc_scaler(d.Entity) > 180 ? `end` :`begin`)
.style('font-family',"Roboto").attr('fill', neutral)

// annotate_grouping.append('text').style('font-size', 12)
// .style('fill', 'whitesmoke')
// .attr('dominant-baseline', 'central')
// .attr('transform', d => ((x(d.Title) + x.step()/2) > Math.PI) ? `rotate(360)` :`rotate(180)`)
// .attr('text-anchor', d => ((x(d.Title) + x.step()/2) > Math.PI) ? `begin` :`end`)
// .text(d => d.Title).style('font-size', '12')



groups.append('circle')
.attr("cx", x => stem_scaler(x.mismanaged_pw_capita))
.attr("cy", 0)
.attr("r", x => leaf_scaler(x.mismanaged_pw_total))
.attr('stroke-width', 1)
.style("fill",'#293241')
.attr("opacity", 0.7)
// .style("stroke", "gray")

groups.append('circle')
.attr("cx", x => stem_scaler(x.mismanaged_pw_capita) + total_pw_scaler(x.pw_total) + leaf_scaler(x.mismanaged_pw_total) + 10)
.attr("cy", 0)
.attr("r", 1)
.style("fill",'black')
.attr("opacity", 1)



groups.append('circle')
.attr("cx", x => stem_scaler(x.mismanaged_pw_capita))
.attr("cy", 0)
.attr("r", 1)
.style("fill",'white')
.attr("opacity", 0.6)

groups.append('circle')
.attr('cx', x => {
let total_pw_len = total_pw_scaler(x.pw_total) ? total_pw_scaler(x.pw_total) : 1
let total_len = stem_scaler(x.mismanaged_pw_capita) + total_pw_len*2 + leaf_scaler(x.mismanaged_pw_total) + 20
return total_len
})
.attr("cy", 0)
.attr("r", 1)
.style("fill",'black')
.attr("opacity", 1)

// svg.append("circle")
// .attr('cx', 100)
// .attr('cy', 200)
// .attr("r", 100)
// .style("fill", t.url());

// svg.append("circle")
// .attr('cx', 100)
// .attr('cy', 30) transform-origin: center;
// .attr("r", 30)
// .style("fill", z.url());
// const g = svg.append('g').attr('transform', `translate(${25}, ${25}}`)
// g.append('path').attr("transform", `translate(${25}, ${25}) rotate(20, 0, 0)`).attr('d',`M 0 0 C 0 -${stem-2} -${p_width}
// -${stem-p_width} -${p_width} -${stem} C -${p_width} -${stem+p_width*1.2} ${p_width} -${stem+p_width*1.2} ${p_width} -${stem} C ${p_width} -${stem-p_width} 0 -${stem-2} 0 0`).style("fill",'tomato')


// M 0 0 C 0 -${stem} -${p_width} -5 -2 -7 C -2 -10 2 -10 2 -7 C 2 -5 0 -2 0 0
return svg.node()
}
Insert cell
plastic_dataset = FileAttachment("plastic_dataset.json").json()
Insert cell
plastic_waste_world.length
Insert cell
arc_scaler = d3.scaleBand().range([0, 360]).domain(plastic_waste_world.map(x => x.Entity))
Insert cell
stem_scaler = d3.scaleSqrt().range([inner_radius,stem_x1]).domain([0, d3.extent(plastic_waste_world.map(x => x.mismanaged_pw_capita))[1]])
Insert cell
leaf_scaler = d3.scaleLinear().range([2, leaf_width]).domain(d3.extent(plastic_waste_world.map(x => x.mismanaged_pw_total)))
Insert cell
total_pw_scaler = d3.scaleLinear().range([5, total_x]).domain(d3.extent(plastic_waste_world.map(x => x.pw_total )))
Insert cell
plastic_waste_world_sort = plastic_waste_world.sort((a,b) => b.mismanaged_pw_capita - a.mismanaged_pw_capita)
Insert cell
arc_step = 360 / plastic_waste_world.length
Insert cell
arc_scaler.step()
Insert cell
background = "whitesmoke"
Insert cell
petal_color = "#fc6f79"
Insert cell
stroke_line = '#1f1f1f'
Insert cell
pw_total_color = 'tomato'
Insert cell
text_color = 'black'
Insert cell
stem_scaler1 = d3.scaleSqrt().range([stem_x1, inner_radius+20]).domain(d3.extent(plastic_waste_world.map(x => x.mismanaged_pw_capita)))
Insert cell
Insert cell
import { legendCircle } from "@harrystevens/circle-legend";

Insert cell
legend = legendCircle()
.scale(total_pw_scaler)
.tickValues([10000000, 30000000, 50000000])
.tickFormat((d, i, e) => i === e.length - 1 ? d + " Tonnes of trash" : d)
.tickSize(2); // defaults to 5
Insert cell
legend2 = legendCircle()
.scale(leaf_scaler)
.tickValues([50000, 150000, 300000])
.tickFormat((d, i, e) => i === e.length - 1 ? d + " bushels of hay" : d)
.tickSize(2); // defaults to 5
Insert cell
xAxis = g => g
.call(d3.axisLeft(stem_scaler).ticks(4))
.call(g => g.select('.domain').remove())
// .call(g => g.select('text').remove())
// .call(g => g.selectAll('.tick line').remove())
Insert cell
fontName = "Roboto"
Insert cell
style = {
yield html`<style>
@import url('https://fonts.googleapis.com/css2?family=${fontName}&display=swap');

div, input, button {
font-family: '${fontName}', sans-serif;
}
</style>`
}
Insert cell
leaf_scaler.domain()
Insert cell
plastic_waste_world@1.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
{var svg = document.getElementById("svg-vis1");

//get svg source.
var serializer = new XMLSerializer();
var source = serializer.serializeToString(svg);

//add name spaces.
if(!source.match(/^<svg[^>]+xmlns="http\:\/\/www\.w3\.org\/2000\/svg"/)){
source = source.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
}
if(!source.match(/^<svg[^>]+"http\:\/\/www\.w3\.org\/1999\/xlink"/)){
source = source.replace(/^<svg/, '<svg xmlns:xlink="http://www.w3.org/1999/xlink"');
}

//add xml declaration
source = '<?xml version="1.0" standalone="no"?>\r\n' + source;

//convert svg source to URI data scheme.
var url = "data:image/svg+xml;charset=utf-8,"+encodeURIComponent(source);

//set url value to a element's href attribute.
document.getElementById("link").href = url;
}
Insert cell
g = {

const viz = d3.create("div")
const svg = viz.append("svg").attr("id","svg2")
.attr("width",300)
.attr("height",300)
.attr("viewBox",[0,0,300, 300])

var t = textures.lines()
.orientation("3/8")
.stroke("darkorange");

svg.call(t);

svg.append("circle")
.attr('cx', 100)
.attr('cy', 200)
.attr("r", 50)
.style("fill", t.url());

return svg.node()
}
Insert cell
textures = require('textures@1.2.0/dist/textures.js')
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