{"id":"e039bf153c406e38","slug":"draggable-pie-donut-chart","first_public_version":898,"paused_version":null,"likes":27,"publish_level":"live","forks":4,"fork_of":{"id":"50064204b82fdf22","slug":"pie-chart-component","title":"Pie chart component","owner":{"id":"863951e3ebe4c0ae","avatar_url":"https://avatars.observableusercontent.com/avatar/5af16e327a90b2873351dda8a596c0d2d3bf954f64523deefe80177c9764d0f7","login":"d3","name":"D3","bio":"Bring your data to life.","home_url":"https://d3js.org","type":"team","tier":"pro_2024"},"version":353},"has_importers":false,"thumbnail":"1e87dce8c7ba1e90e0ae857038d1bbaa6ac06bf80669005df52460f6b30ff356","default_thumbnail":"1e87dce8c7ba1e90e0ae857038d1bbaa6ac06bf80669005df52460f6b30ff356","update_time":"2020-07-29T12:00:08.223Z","publish_time":"2023-09-12T09:34:51.413Z","publish_version":898,"latest_version":898,"roles":[],"sharing":null,"owner":{"id":"1a55a189adb52dd7","avatar_url":"https://avatars.observableusercontent.com/avatar/39220b21cbf7eff8318ad71b8f1f1b73aaaa40953e51c89e2bf097d8e901318c","login":"juba","name":"Julien Barnier","bio":"Trying to do things.","home_url":"https://data.nozav.org","type":"team","tier":"starter_2024"},"creator":{"id":"ef385df54c3fe400","avatar_url":"https://avatars.observableusercontent.com/avatar/39220b21cbf7eff8318ad71b8f1f1b73aaaa40953e51c89e2bf097d8e901318c","login":"juba","name":"Julien Barnier","bio":"Trying to do things.","home_url":"https://data.nozav.org","tier":"public"},"authors":[{"id":"ef385df54c3fe400","avatar_url":"https://avatars.observableusercontent.com/avatar/39220b21cbf7eff8318ad71b8f1f1b73aaaa40953e51c89e2bf097d8e901318c","name":"Julien Barnier","login":"juba","bio":"Trying to do things.","home_url":"https://data.nozav.org","tier":"public","approved":true,"description":""}],"files":[{"id":"bee673b386dd058ab8d2cf353acbedc6aa01ebd1e6f63e2a9ab1b4273c7e6efd1eeea526345e4be7f0012d5db3ec743ef39ad9e6a043c196670bf9658cb02e79","url":"https://static.observableusercontent.com/files/bee673b386dd058ab8d2cf353acbedc6aa01ebd1e6f63e2a9ab1b4273c7e6efd1eeea526345e4be7f0012d5db3ec743ef39ad9e6a043c196670bf9658cb02e79","download_url":"https://static.observableusercontent.com/files/bee673b386dd058ab8d2cf353acbedc6aa01ebd1e6f63e2a9ab1b4273c7e6efd1eeea526345e4be7f0012d5db3ec743ef39ad9e6a043c196670bf9658cb02e79?response-content-disposition=attachment%3Bfilename*%3DUTF-8%27%27population-by-age.csv","name":"population-by-age.csv","create_time":"2020-07-28T09:03:01.872Z","mime_type":"text/csv","status":"public","size":273,"content_encoding":"gzip","private_bucket_id":null}],"comments":[],"commenting_lock":null,"suggestions_to":[],"suggestion_from":null,"collections":[{"id":"7ce57e5117a4f35d","type":"public","slug":"robservable","title":"robservable","description":"","update_time":"2020-07-02T09:30:42.639Z","pinned":false,"ordered":false,"custom_thumbnail":null,"default_thumbnail":"3c0eb3256ff2525b2a55991f8e0f23877a4775d6eab62297c43f308d85f96072","thumbnail":"3c0eb3256ff2525b2a55991f8e0f23877a4775d6eab62297c43f308d85f96072","listing_count":5,"parent_collection_count":0,"owner":{"id":"1a55a189adb52dd7","avatar_url":"https://avatars.observableusercontent.com/avatar/39220b21cbf7eff8318ad71b8f1f1b73aaaa40953e51c89e2bf097d8e901318c","login":"juba","name":"Julien Barnier","bio":"Trying to do things.","home_url":"https://data.nozav.org","type":"team","tier":"starter_2024"}}],"version":898,"title":"Draggable Pie/Donut Chart","license":"isc","copyright":"Copyright 2018–2020 Observable, Inc.","nodes":[{"id":0,"value":"md`# Draggable Pie/Donut Chart\n\nAdapted from Mike Bostock's [Pie Chart](https://observablehq.com/@d3/pie-chart) notebook and David Buezas's [Pie Chart Labels](http://bl.ocks.org/dbuezas/9572040) block.\n\nSlices are draggable.\n\n[robservable](https://juba.github.io/robservable/) users can call it from R with something like the following. Use the <code>input</code> named list to customize the [settings](#sec_settings) :\n\n\\`\\`\\`r\n## Data must be a data frame with 'name' and 'value' columns\ndf <- data.frame(table(mtcars$cyl))\nnames(df) <- c(\"name\", \"value\")\n## Call to robservable\nrobservable(\n  \"https://observablehq.com/d/e039bf153c406e38\", \n  include = c(\"chart\", \"draw\"),\n  hide = \"draw\",\n  input = list(\n    data = df\n  )\n)\n\\`\\`\\`\n`","pinned":false,"mode":"js","data":null,"name":null},{"id":17,"value":"chart = {\n  const svg = d3\n    .create(\"svg\")\n    .attr(\"viewBox\", [-width / 2, -height / 2, width, height]);\n\n  const t = svg\n    .transition()\n    .duration(transition_speed)\n    .ease(d3.easeLinear)\n    .end();\n\n  const g_arcs = svg.append(\"g\").attr(\"stroke\", \"white\");\n\n  const g_labels = svg\n    .append(\"g\")\n    .attr(\"font-family\", \"sans-serif\")\n    .attr(\"font-size\", 12)\n    .attr(\"text-anchor\", \"middle\");\n\n  const g_lines = svg.append(\"g\");\n\n  function update(dat) {\n    let arcs = pie(dat);\n\n    function dragstart(d, i) {\n      g_arcs\n        .selectAll(\"path\")\n        .attr(\"opacity\", (dd, ii) =>\n          d.data.pie_key == dd.data.pie_key ? 1 : 0.3\n        );\n    }\n    function drag(d, index) {\n      if (\n        mutable current_order !== null &&\n        d.data.pie_order != mutable current_order\n      ) {\n        const new_order = mutable current_order;\n        dat = dat.map(dd => {\n          if (dd.pie_order == mutable current_order) {\n            dd.pie_order = d.data.pie_order;\n          }\n          return dd;\n        });\n        dat[index].pie_order = new_order;\n        update(dat);\n      }\n    }\n\n    function dragend(d, i) {\n      d3.selectAll(\"path\").attr(\"opacity\", 1);\n    }\n\n    g_arcs\n      .selectAll(\"path\")\n      .data(arcs, d => d.data.pie_key)\n      .join(\n        enter =>\n          enter.append(\"path\").call(selection => {\n            if (dragging) {\n              selection\n                .call(\n                  d3\n                    .drag()\n                    .on(\"start\", dragstart)\n                    .on(\"drag\", drag)\n                    .on(\"end\", dragend)\n                )\n                .style(\"cursor\", \"move\");\n            }\n            selection\n              .attr(\"fill\", d => color(d.data.name))\n              .attr(\"d\", arc)\n              .on(\"mouseover\", pie_in)\n              .on(\"mouseout\", pie_out)\n              .append(\"title\")\n              .text(d => `${d.data.name}\\n${d.data.value.toLocaleString()}`);\n          }),\n        update =>\n          update.call(selection => selection.transition(t).attr(\"d\", arc))\n      );\n\n    g_labels\n      .selectAll(\"text\")\n      .data(arcs, d => d.data.pie_key)\n      .join(\n        enter =>\n          enter.append(\"text\").call(selection =>\n            selection\n              .attr(\"dy\", \".35em\")\n              .attr(\"transform\", d => `translate(${label_position(d)})`)\n              .style(\"text-anchor\", d => label_anchor(d))\n              .call(text => text.append(\"tspan\").text(d => d.data.name))\n          ),\n        update =>\n          update.call(selection =>\n            selection\n              .transition(t)\n              .style(\"text-anchor\", d => label_anchor(d))\n              .attr(\"transform\", d => `translate(${label_position(d)})`)\n          )\n      );\n\n    g_lines\n      .selectAll(\"polyline\")\n      .data(arcs, d => d.data.pie_key)\n      .join(\n        enter =>\n          enter\n            .append(\"polyline\")\n            .style(\"fill\", \"none\")\n            .style(\"opacity\", 0.5)\n            .style(\"stroke\", \"black\")\n            .style(\"stroke-width\", \"1px\")\n            .call(selection => selection.attr(\"points\", d => line_position(d))),\n        update =>\n          update.call(selection =>\n            selection.transition(t).attr(\"points\", d => line_position(d))\n          )\n      );\n  }\n\n  //update(pie_data);\n\n  return Object.assign(svg.node(), { update });\n}","pinned":false,"mode":"js","data":null,"name":null},{"id":854,"value":"draw = chart.update(pie_data)","pinned":false,"mode":"js","data":null,"name":null},{"id":629,"value":"sec_settings = md`## Settings`","pinned":false,"mode":"js","data":null,"name":null},{"id":746,"value":"md`Set <code>donut_proportion</code> to 0 to make a pie chart, and to a number between 0 and 1 to make a donut chart.`","pinned":false,"mode":"js","data":null,"name":null},{"id":648,"value":"donut_proportion = 0.7","pinned":false,"mode":"js","data":null,"name":null},{"id":886,"value":"md`Set <code>dragging</code> to <code>false</code> to disable slice dragging.`","pinned":false,"mode":"js","data":null,"name":null},{"id":875,"value":"dragging = true","pinned":false,"mode":"js","data":null,"name":null},{"id":613,"value":"palette = \"schemePaired\"","pinned":false,"mode":"js","data":null,"name":null},{"id":12,"value":"height = Math.min(width, 500)","pinned":false,"mode":"js","data":null,"name":null},{"id":716,"value":"transition_speed = 200","pinned":false,"mode":"js","data":null,"name":null},{"id":192,"value":"data = d3.csvParse(await FileAttachment(\"population-by-age.csv\").text(), d3.autoType)","pinned":false,"mode":"js","data":null,"name":null},{"id":619,"value":"md`## Notebook code`","pinned":false,"mode":"js","data":null,"name":null},{"id":841,"value":"html`<style>\npolyline {\n  stroke: black;\n  fill: none;\n  stroke-width: 1px;\n  opacity: 0.5;\n}\n</style>`","pinned":false,"mode":"js","data":null,"name":null},{"id":358,"value":"mutable debug = null","pinned":false,"mode":"js","data":null,"name":null},{"id":555,"value":"mutable current_order = null","pinned":false,"mode":"js","data":null,"name":null},{"id":453,"value":"pie_data = data.map((d, i) => Object.assign(d, { pie_key: i, pie_order: i }))","pinned":false,"mode":"js","data":null,"name":null},{"id":539,"value":"pie_in = function(d, i) {\n  mutable current_order = d.data.pie_order;\n}","pinned":false,"mode":"js","data":null,"name":null},{"id":543,"value":"pie_out = function(d, i) {\n  mutable current_order = null;\n}","pinned":false,"mode":"js","data":null,"name":null},{"id":35,"value":"color = d3.scaleOrdinal(d3[palette])\n    .domain(data.map(d => d.name))","pinned":false,"mode":"js","data":null,"name":null},{"id":657,"value":"outer_radius = Math.min(width, height) / 2 - 1","pinned":false,"mode":"js","data":null,"name":null},{"id":792,"value":"inner_radius = outer_radius * 0.8","pinned":false,"mode":"js","data":null,"name":null},{"id":785,"value":"outer_pie_radius = outer_radius * 0.8","pinned":false,"mode":"js","data":null,"name":null},{"id":787,"value":"inner_pie_radius = outer_pie_radius * donut_proportion","pinned":false,"mode":"js","data":null,"name":null},{"id":768,"value":"line_position = function(d) {\n  let pos = arcLabel.centroid(d);\n  pos[0] = outer_radius * 0.95 * (midAngle(d) < Math.PI ? 1 : -1);\n  return [arcLine.centroid(d), arcLabel.centroid(d), pos];\n}","pinned":false,"mode":"js","data":null,"name":null},{"id":777,"value":"label_anchor = function(d) {\n  return midAngle(d) < Math.PI ? \"start\" : \"end\";\n}","pinned":false,"mode":"js","data":null,"name":null},{"id":762,"value":"label_position = function(d) {\n  let pos = arcLabel.centroid(d);\n  pos[0] = outer_radius * (midAngle(d) < Math.PI ? 1 : -1);\n  return pos;\n}","pinned":false,"mode":"js","data":null,"name":null},{"id":756,"value":"midAngle = d => d.startAngle + (d.endAngle - d.startAngle) / 2","pinned":false,"mode":"js","data":null,"name":null},{"id":22,"value":"arc = d3\n  .arc()\n  .innerRadius(inner_pie_radius)\n  .outerRadius(outer_pie_radius)","pinned":false,"mode":"js","data":null,"name":null},{"id":306,"value":"arcLabel = d3\n  .arc()\n  .innerRadius(outer_radius * 0.95)\n  .outerRadius(outer_radius * 0.95)","pinned":false,"mode":"js","data":null,"name":null},{"id":806,"value":"arcLine = d3\n  .arc()\n  .innerRadius(Math.max(inner_pie_radius, outer_pie_radius * 0.8))\n  .outerRadius(outer_pie_radius)","pinned":false,"mode":"js","data":null,"name":null},{"id":19,"value":"pie = d3\n  .pie()\n  .value(d => d.value)\n  .sort((a, b) => {\n    return a.pie_order - b.pie_order;\n  })","pinned":false,"mode":"js","data":null,"name":null},{"id":6,"value":"d3 = require(\"d3@5\")","pinned":false,"mode":"js","data":null,"name":null}],"resolutions":[],"schedule":null,"last_view_time":null}