{"id":"e68ad55af4b37234","slug":"difference-chart","trashed":false,"description":"","likes":64,"publish_level":"live_unlisted","forks":28,"fork_of":null,"has_importers":true,"update_time":"2024-09-23T09:40:31.643Z","first_public_version":261,"paused_version":null,"publish_time":"2023-06-29T19:35:46.699Z","publish_version":263,"latest_version":263,"thumbnail":"f85f4fecdefa5c4dd89d498afa011089e38a91c70b590826cdf262a6f4763ac1","default_thumbnail":"f85f4fecdefa5c4dd89d498afa011089e38a91c70b590826cdf262a6f4763ac1","roles":[],"sharing":null,"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"},"creator":{"id":"074c414ad1d825f5","avatar_url":"https://avatars.observableusercontent.com/avatar/82811927da99f8938001b2ef1f552ad2c47083e46ebc55a3a146a5a5848c4519","login":"mbostock","name":"Mike Bostock","bio":"Visualization toolmaker. Founder @observablehq. Creator @d3. Former @nytgraphics. Pronounced BOSS-tock.","home_url":"https://bost.ocks.org/mike/","tier":"pro"},"authors":[{"id":"074c414ad1d825f5","avatar_url":"https://avatars.observableusercontent.com/avatar/82811927da99f8938001b2ef1f552ad2c47083e46ebc55a3a146a5a5848c4519","name":"Mike Bostock","login":"mbostock","bio":"Visualization toolmaker. Founder @observablehq. Creator @d3. Former @nytgraphics. Pronounced BOSS-tock.","home_url":"https://bost.ocks.org/mike/","tier":"pro","approved":true,"description":""}],"collections":[{"id":"2a5376aca044b34e","type":"public","slug":"charts","title":"D3 Charts","description":"Older D3 examples, deprecated.","update_time":"2023-07-04T09:37:08.122Z","pinned":false,"ordered":true,"custom_thumbnail":null,"default_thumbnail":"2471c1145e33b39a1d5e38b7bb5460a2620aadf1c32717a270347620b1e981bd","thumbnail":"2471c1145e33b39a1d5e38b7bb5460a2620aadf1c32717a270347620b1e981bd","listing_count":11,"parent_collection_count":0,"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"}}],"files":[{"id":"4adc98b10d124a9baedf976477b8848feecba26cc94672f2b1dc4fbc94c71c67635eb65915c37ad2e4c3d606ddd00b9dc62fde6f4515e399574907f9c0bf6431","url":"https://static.observableusercontent.com/files/4adc98b10d124a9baedf976477b8848feecba26cc94672f2b1dc4fbc94c71c67635eb65915c37ad2e4c3d606ddd00b9dc62fde6f4515e399574907f9c0bf6431","download_url":"https://static.observableusercontent.com/files/4adc98b10d124a9baedf976477b8848feecba26cc94672f2b1dc4fbc94c71c67635eb65915c37ad2e4c3d606ddd00b9dc62fde6f4515e399574907f9c0bf6431?response-content-disposition=attachment%3Bfilename*%3DUTF-8%27%27weather.tsv","name":"weather.tsv","create_time":"2019-10-29T20:52:03.283Z","mime_type":"text/tab-separated-values","status":"public","size":6982,"content_encoding":"gzip","private_bucket_id":null},{"id":"9a51cecd78df89f1d88a1011ae7f4a90b9e135692651f189e87e44e42c0a8da2b2c58fd5db2a59153e15f2e4b477017b67a77a635945ea61187dce398ae8dfe3","url":"https://static.observableusercontent.com/files/9a51cecd78df89f1d88a1011ae7f4a90b9e135692651f189e87e44e42c0a8da2b2c58fd5db2a59153e15f2e4b477017b67a77a635945ea61187dce398ae8dfe3","download_url":"https://static.observableusercontent.com/files/9a51cecd78df89f1d88a1011ae7f4a90b9e135692651f189e87e44e42c0a8da2b2c58fd5db2a59153e15f2e4b477017b67a77a635945ea61187dce398ae8dfe3?response-content-disposition=attachment%3Bfilename*%3DUTF-8%27%27weather.csv","name":"weather.csv","create_time":"2021-09-28T14:48:52.311Z","mime_type":"text/csv","status":"public","size":7713,"content_encoding":"gzip","private_bucket_id":null}],"comments":[{"id":"d0057cf4f0649378","content":"Should this use \"step-after\" instead of \"step\" for the curve? It seems like that results in the line matching the rects better.","node_id":168,"create_time":"2021-12-29T18:12:19.739Z","update_time":null,"resolved":true,"user":{"id":"2a6984a21bf6bf27","avatar_url":"https://avatars.observableusercontent.com/avatar/433f8326248db53a8d1ead342dfc4d46c845f915b422b6e7f6690ced0f18abd2","login":"rlesser","name":"Robert Lesser","bio":"Product Engineer at Nomic AI | Building ways to see what's around us","home_url":"https://twitter.com/RobertLesser_","tier":"pro"}},{"id":"d93478b7e8d7e21b","content":"Yes, thank you for the suggestion!","node_id":168,"create_time":"2022-02-19T16:39:19.731Z","update_time":null,"resolved":true,"user":{"id":"45a379fcfcb14253","avatar_url":"https://avatars.observableusercontent.com/avatar/9cf371db2e1a2b4ed5f4a9783b6954cf7aeb2a88c56d4054c96ef360cf85ef89","login":"fil","name":"Fil","bio":"Vocateur.","home_url":"https://visionscarto.net/","tier":"pro"}}],"commenting_lock":null,"suggestion_from":null,"suggestions_to":[],"version":263,"title":"Area Chart, Difference","license":"isc","copyright":"Copyright 2018–2021 Observable, Inc.","nodes":[{"id":0,"value":"# Area Chart, Difference\n\n<p style=\"background: #fffced; box-sizing: border-box; padding: 10px 20px; font-style: italic;\">**Update June 2023:** This notebook has been deprecated and is no longer maintained; please see the newer [D3 difference chart](/@d3/difference-chart/2) and [Observable Plot difference chart](/@observablehq/plot-difference-chart) examples.</p>\n\nThe temperature in San Francisco compared to New York; days when San Francisco was warmer are orange, and colder days are blue.","pinned":false,"mode":"md","data":null,"name":""},{"id":136,"value":"chart = DifferenceChart(weather, {\n  x: d => d.date,\n  y1: d => d[\"New York\"],\n  y2: d => d[\"San Francisco\"],\n  yLabel: \"↑ Temperature (°F)\",\n  colors: d3.reverse(d3.schemeRdBu[3]),\n  curve: d3.curveStep,\n  width,\n  height: 600\n})","pinned":true,"mode":"js","data":null,"name":null},{"id":139,"value":"weather = FileAttachment(\"weather.csv\").csv({typed: true})","pinned":true,"mode":"js","data":null,"name":null},{"id":142,"value":"howto(\"DifferenceChart\")","pinned":false,"mode":"js","data":null,"name":null},{"id":168,"value":"altplot(`Plot.plot({\n  width,\n  marks: [\n    Plot.rect(weather, {\n      x: \"date\",\n      y1: \"New York\",\n      y2: \"San Francisco\",\n      fill: d => d[\"New York\"] > d[\"San Francisco\"],\n      interval: d3.utcDay,\n      inset: -0.2\n    }),\n    Plot.line(weather, {\n      x: \"date\",\n      y: \"New York\",\n      curve: \"step-after\"\n    })\n  ],\n  color: { domain: [false, true], range: [\"#91bfdb\", \"#fc8d59\"] }\n})\n// for an alternative using clip-paths, see https://observablehq.com/@fil/plot-difference-chart\n`)","pinned":false,"mode":"js","data":null,"name":null},{"id":39,"value":"function DifferenceChart(data, {\n  x = ([x]) => x, // given d in data, returns the (temporal) x-value\n  y1 = () => 0, // given d in data, returns the (quantitative) baseline y-value\n  y2 = ([, y]) => y, // given d in data, returns the (quantitative) y-value\n  defined, // for gaps in data\n  curve = d3.curveLinear, // curve generator for the lines\n  marginTop = 20, // top margin, in pixels\n  marginRight = 30, // right margin, in pixels\n  marginBottom = 30, // bottom margin, in pixels\n  marginLeft = 40, // left margin, in pixels\n  width = 640, // outer width, in pixels\n  height = 400, // outer height, in pixels\n  xType = d3.scaleUtc, // type of x-scale\n  xDomain, // [xmin, xmax]\n  xRange = [marginLeft, width - marginRight], // [left, right]\n  yType = d3.scaleLinear, // type of y-scale\n  yDomain, // [ymin, ymax]\n  yRange = [height - marginBottom, marginTop], // [bottom, top]\n  yFormat, // a format specifier string for the y-axis\n  yLabel, // a label for the y-axis\n  colors = d3.schemePiYG[3], // colors for negative and positive differences\n  stroke = \"currentColor\", // stroke color of line\n  strokeLinecap = \"round\", // stroke line cap of the line\n  strokeLinejoin = \"round\", // stroke line join of the line\n  strokeWidth = 1.5, // stroke width of line, in pixels\n  strokeOpacity = 1, // stroke opacity of line\n} = {}) {\n  // Compute values.\n  const X = d3.map(data, x);\n  const Y1 = d3.map(data, y1);\n  const Y2 = d3.map(data, y2);\n  const I = d3.range(data.length);\n  if (defined === undefined) defined = (d, i) => !isNaN(X[i]) && !isNaN(Y1[i]) && !isNaN(Y2[i]);\n  const D = d3.map(data, defined);\n\n  // Compute default domains.\n  if (xDomain === undefined) xDomain = d3.extent(X);\n  if (yDomain === undefined) yDomain = d3.extent([...Y1, ...Y2]);\n\n  // Construct scales and axes.\n  const xScale = xType(xDomain, xRange);\n  const yScale = yType(yDomain, yRange);\n  const xAxis = d3.axisBottom(xScale).ticks(width / 80).tickSizeOuter(0);\n  const yAxis = d3.axisLeft(yScale).ticks(height / 60, yFormat);\n\n  // A unique identifier for clip paths (to avoid conflicts).\n  const uid = `O-${Math.random().toString(16).slice(2)}`;\n\n  // Helpers for rendering lines and areas.\n  const line = (y) => d3.line().defined(i => D[i]).curve(curve).x(i => xScale(X[i])).y(y)(I);\n  const area = (y0, y1) => d3.area().defined(i => D[i]).curve(curve).x(i => xScale(X[i])).y0(y0).y1(y1)(I);\n\n  const svg = d3.create(\"svg\")\n      .attr(\"width\", width)\n      .attr(\"height\", height)\n      .attr(\"viewBox\", [0, 0, width, height])\n      .attr(\"style\", \"max-width: 100%; height: auto; height: intrinsic;\");\n\n  svg.append(\"g\")\n      .attr(\"transform\", `translate(${marginLeft},0)`)\n      .call(yAxis)\n      .call(g => g.select(\".domain\").remove())\n      .call(g => g.selectAll(\".tick line\").clone()\n          .attr(\"x2\", width - marginLeft - marginRight)\n          .attr(\"stroke-opacity\", 0.1))\n      .call(g => g.append(\"text\")\n          .attr(\"x\", -marginLeft)\n          .attr(\"y\", 10)\n          .attr(\"fill\", \"currentColor\")\n          .attr(\"text-anchor\", \"start\")\n          .text(yLabel));\n\n  svg.append(\"clipPath\")\n      .attr(\"id\", `${uid}-above`)\n    .append(\"path\")\n      .attr(\"d\", area(0, i => yScale(Y1[i])));\n\n  svg.append(\"clipPath\")\n      .attr(\"id\", `${uid}-below`)\n    .append(\"path\")\n      .attr(\"d\", area(height, i => yScale(Y1[i])));\n\n  svg.append(\"path\")\n      .attr(\"clip-path\", `url(${new URL(`#${uid}-above`, location)})`)\n      .attr(\"fill\", colors[colors.length - 1])\n      .attr(\"d\", area(height, i => yScale(Y2[i])));\n\n  svg.append(\"path\")\n      .attr(\"clip-path\", `url(${new URL(`#${uid}-below`, location)})`)\n      .attr(\"fill\", colors[0])\n      .attr(\"d\", area(0, i => yScale(Y2[i])));\n\n  svg.append(\"g\")\n      .attr(\"transform\", `translate(0,${height - marginBottom})`)\n      .call(xAxis)\n      .call(g => g.select(\".domain\").remove());\n\n  svg.append(\"path\")\n      .attr(\"fill\", \"none\")\n      .attr(\"stroke\", stroke)\n      .attr(\"stroke-width\", strokeWidth)\n      .attr(\"stroke-linecap\", strokeLinecap)\n      .attr(\"stroke-linejoin\", strokeLinejoin)\n      .attr(\"stroke-opacity\", strokeOpacity)\n      .attr(\"d\", line(i => yScale(Y2[i])));\n\n  return svg.node();\n}","pinned":true,"mode":"js","data":null,"name":null},{"id":6,"value":"import {howto, altplot} from \"@d3/example-components\"","pinned":true,"mode":"js","data":null,"name":null}],"resolutions":[],"schedule":null,"last_view_time":null}