{"id":"4710d9b97a7416b3","slug":"brushable-scatterplot-matrix","first_public_version":284,"paused_version":315,"likes":143,"publish_level":"live","forks":99,"fork_of":null,"has_importers":true,"thumbnail":"fb78618d6122ce68ce274836fc424b33e1230c20f34b58830d201c1eeed62cab","default_thumbnail":"fb78618d6122ce68ce274836fc424b33e1230c20f34b58830d201c1eeed62cab","update_time":"2020-09-22T16:17:13.814Z","publish_time":"2023-07-04T16:04:22.626Z","publish_version":315,"latest_version":315,"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":""}],"files":[{"id":"8b027d7a50c443314086cfcaa1d77f057c9f2a23ace5ac641b516d97847db125cb331423dc5a00d0b5c8b1992c19aa7330cd01e7fad0b5d8bc93753910b96963","url":"https://static.observableusercontent.com/files/8b027d7a50c443314086cfcaa1d77f057c9f2a23ace5ac641b516d97847db125cb331423dc5a00d0b5c8b1992c19aa7330cd01e7fad0b5d8bc93753910b96963","download_url":"https://static.observableusercontent.com/files/8b027d7a50c443314086cfcaa1d77f057c9f2a23ace5ac641b516d97847db125cb331423dc5a00d0b5c8b1992c19aa7330cd01e7fad0b5d8bc93753910b96963?response-content-disposition=attachment%3Bfilename*%3DUTF-8%27%27iris.csv","name":"iris.csv","create_time":"2019-10-29T23:19:51.004Z","mime_type":"text/csv","status":"public","size":3712,"content_encoding":"gzip","private_bucket_id":null},{"id":"715db1223e067f00500780077febc6cebbdd90c151d3d78317c802732252052ab0e367039872ab9c77d6ef99e5f55a0724b35ddc898a1c99cb14c31a379af80a","url":"https://static.observableusercontent.com/files/715db1223e067f00500780077febc6cebbdd90c151d3d78317c802732252052ab0e367039872ab9c77d6ef99e5f55a0724b35ddc898a1c99cb14c31a379af80a","download_url":"https://static.observableusercontent.com/files/715db1223e067f00500780077febc6cebbdd90c151d3d78317c802732252052ab0e367039872ab9c77d6ef99e5f55a0724b35ddc898a1c99cb14c31a379af80a?response-content-disposition=attachment%3Bfilename*%3DUTF-8%27%27penguins.csv","name":"penguins.csv","create_time":"2020-06-12T20:23:56.042Z","mime_type":"text/csv","status":"public","size":13506,"content_encoding":"gzip","private_bucket_id":null}],"comments":[{"id":"a25eb3c7bab52556","content":"Vertical scale in bottom-left chart is not completely visible - the forth digit of all values is missing compared to the counterpart in bottom right chart.","node_id":22,"create_time":"2021-01-09T20:58:42.750Z","update_time":null,"resolved":true,"user":{"id":"b334a8ee5c1300ad","avatar_url":"https://avatars.observableusercontent.com/avatar/12927e3280589696b1072d0b373d374320d188ffb760422f921bb4955a937f34","login":"pattydepuh","name":"Patrick Schulz","bio":"","home_url":"","tier":"public"}},{"id":"6a9cd89578486603","content":"Thank you. Fixed with a bit more padding.","node_id":22,"create_time":"2021-01-09T21:44:29.081Z","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,"suggestions_to":[],"suggestion_from":null,"collections":[{"id":"796639dc14c91120","type":"public","slug":"data-visualization-examples","title":"Data Visualization Examples","description":"","update_time":"2023-07-19T18:20:24.429Z","pinned":false,"ordered":true,"custom_thumbnail":null,"default_thumbnail":"13ccec36495197ae6ec4b38497f20452aff2a7a3253c411073a0b95595d72b5f","thumbnail":"13ccec36495197ae6ec4b38497f20452aff2a7a3253c411073a0b95595d72b5f","listing_count":15,"parent_collection_count":0,"owner":{"id":"f35c755083683fe5","avatar_url":"https://avatars.observableusercontent.com/avatar/5a51c3b908225a581d20577e488e2aba8cbc9541c52982c638638c370c3e5e8e","login":"observablehq","name":"Observable","bio":"The end-to-end solution for building and hosting better data apps, dashboards, and reports.","home_url":"https://observablehq.com","type":"team","tier":"enterprise_2024"}},{"id":"d9df4fb5263ace62","type":"public","slug":"visualization","title":"Visualization","description":"Explore and explain patterns in quantitative data using D3, Vega, and Observable Plot","update_time":"2023-07-19T17:50:40.230Z","pinned":true,"ordered":true,"custom_thumbnail":"09e385d95ce7df7d392b0133d68e97dd5675378190775d038d21516ea62178ba","default_thumbnail":"32ad600cf556f7b9991b2a11f7b6b8e55abe7ada6062ef88a5a7de422bb261ab","thumbnail":"09e385d95ce7df7d392b0133d68e97dd5675378190775d038d21516ea62178ba","listing_count":84,"parent_collection_count":1,"owner":{"id":"f35c755083683fe5","avatar_url":"https://avatars.observableusercontent.com/avatar/5a51c3b908225a581d20577e488e2aba8cbc9541c52982c638638c370c3e5e8e","login":"observablehq","name":"Observable","bio":"The end-to-end solution for building and hosting better data apps, dashboards, and reports.","home_url":"https://observablehq.com","type":"team","tier":"enterprise_2024"}},{"id":"1b0fbc3b1939d7fa","type":"public","slug":"gallery","title":"Gallery","description":"Examples featured in the D3 gallery","update_time":"2023-04-10T02:28:10.124Z","pinned":false,"ordered":false,"custom_thumbnail":null,"default_thumbnail":"6953adf7aec9d29339751fa68d9c9dc67169a0ffd7469f9a4055eda920bdd2a4","thumbnail":"6953adf7aec9d29339751fa68d9c9dc67169a0ffd7469f9a4055eda920bdd2a4","listing_count":147,"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"}},{"id":"66f1a21d8483ddb5","type":"public","slug":"d3-brush","title":"d3-brush","description":"Select a one- or two-dimensional region using the mouse or touch.","update_time":"2019-03-04T18:04:17.784Z","pinned":false,"ordered":false,"custom_thumbnail":null,"default_thumbnail":"1691829c381c71efa117fe6362df073806fbd5e007e5f7ede60e5a8b1f54601e","thumbnail":"1691829c381c71efa117fe6362df073806fbd5e007e5f7ede60e5a8b1f54601e","listing_count":15,"parent_collection_count":1,"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":270,"title":"Brushable Scatterplot Matrix","license":"isc","copyright":"Copyright 2019–2020 Observable, Inc.","nodes":[{"id":0,"value":"md`# Brushable Scatterplot Matrix\n\nThis [scatterplot matrix](/@d3/scatterplot-matrix) allows brushing to select data points in one cell, and highlight them across all other cells.`","pinned":false,"mode":"js","data":null,"name":null},{"id":250,"value":"swatches({color: z})","pinned":false,"mode":"js","data":null,"name":null},{"id":22,"value":"viewof selection = {\n  const svg = d3.create(\"svg\")\n      .attr(\"viewBox\", [-padding, 0, width, width]);\n\n  svg.append(\"style\")\n      .text(`circle.hidden { fill: #000; fill-opacity: 1; r: 1px; }`);\n\n  svg.append(\"g\")\n      .call(xAxis);\n\n  svg.append(\"g\")\n      .call(yAxis);\n\n  const cell = svg.append(\"g\")\n    .selectAll(\"g\")\n    .data(d3.cross(d3.range(columns.length), d3.range(columns.length)))\n    .join(\"g\")\n      .attr(\"transform\", ([i, j]) => `translate(${i * size},${j * size})`);\n\n  cell.append(\"rect\")\n      .attr(\"fill\", \"none\")\n      .attr(\"stroke\", \"#aaa\")\n      .attr(\"x\", padding / 2 + 0.5)\n      .attr(\"y\", padding / 2 + 0.5)\n      .attr(\"width\", size - padding)\n      .attr(\"height\", size - padding);\n\n  cell.each(function([i, j]) {\n    d3.select(this).selectAll(\"circle\")\n      .data(data.filter(d => !isNaN(d[columns[i]]) && !isNaN(d[columns[j]])))\n      .join(\"circle\")\n        .attr(\"cx\", d => x[i](d[columns[i]]))\n        .attr(\"cy\", d => y[j](d[columns[j]]));\n  });\n\n  const circle = cell.selectAll(\"circle\")\n      .attr(\"r\", 3.5)\n      .attr(\"fill-opacity\", 0.7)\n      .attr(\"fill\", d => z(d.species));\n\n  cell.call(brush, circle, svg);\n\n  svg.append(\"g\")\n      .style(\"font\", \"bold 10px sans-serif\")\n      .style(\"pointer-events\", \"none\")\n    .selectAll(\"text\")\n    .data(columns)\n    .join(\"text\")\n      .attr(\"transform\", (d, i) => `translate(${i * size},${i * size})`)\n      .attr(\"x\", padding)\n      .attr(\"y\", padding)\n      .attr(\"dy\", \".71em\")\n      .text(d => d);\n\n  svg.property(\"value\", [])\n  return svg.node();\n}","pinned":true,"mode":"js","data":null,"name":null},{"id":263,"value":"selection","pinned":true,"mode":"js","data":null,"name":null},{"id":214,"value":"function brush(cell, circle, svg) {\n  const brush = d3.brush()\n      .extent([[padding / 2, padding / 2], [size - padding / 2, size - padding / 2]])\n      .on(\"start\", brushstarted)\n      .on(\"brush\", brushed)\n      .on(\"end\", brushended);\n\n  cell.call(brush);\n\n  let brushCell;\n\n  // Clear the previously-active brush, if any.\n  function brushstarted() {\n    if (brushCell !== this) {\n      d3.select(brushCell).call(brush.move, null);\n      brushCell = this;\n    }\n  }\n\n  // Highlight the selected circles.\n  function brushed({selection}, [i, j]) {\n    let selected = [];\n    if (selection) {\n      const [[x0, y0], [x1, y1]] = selection; \n      circle.classed(\"hidden\",\n        d => x0 > x[i](d[columns[i]])\n          || x1 < x[i](d[columns[i]])\n          || y0 > y[j](d[columns[j]])\n          || y1 < y[j](d[columns[j]]));\n      selected = data.filter(\n        d => x0 < x[i](d[columns[i]])\n          && x1 > x[i](d[columns[i]])\n          && y0 < y[j](d[columns[j]])\n          && y1 > y[j](d[columns[j]]));\n    }\n    svg.property(\"value\", selected).dispatch(\"input\");\n  }\n\n  // If the brush is empty, select all circles.\n  function brushended({selection}) {\n    if (selection) return;\n    svg.property(\"value\", []).dispatch(\"input\");\n    circle.classed(\"hidden\", false);\n  }\n}","pinned":true,"mode":"js","data":null,"name":null},{"id":24,"value":"x = columns.map(c => d3.scaleLinear()\n    .domain(d3.extent(data, d => d[c]))\n    .rangeRound([padding / 2, size - padding / 2]))","pinned":true,"mode":"js","data":null,"name":null},{"id":34,"value":"y = x.map(x => x.copy().range([size - padding / 2, padding / 2]))","pinned":true,"mode":"js","data":null,"name":null},{"id":79,"value":"z = d3.scaleOrdinal()\n    .domain(data.map(d => d.species))\n    .range(d3.schemeCategory10)","pinned":true,"mode":"js","data":null,"name":null},{"id":47,"value":"xAxis = {\n  const axis = d3.axisBottom()\n      .ticks(6)\n      .tickSize(size * columns.length);\n  return g => g.selectAll(\"g\").data(x).join(\"g\")\n      .attr(\"transform\", (d, i) => `translate(${i * size},0)`)\n      .each(function(d) { return d3.select(this).call(axis.scale(d)); })\n      .call(g => g.select(\".domain\").remove())\n      .call(g => g.selectAll(\".tick line\").attr(\"stroke\", \"#ddd\"));\n}","pinned":true,"mode":"js","data":null,"name":null},{"id":66,"value":"yAxis = {\n  const axis = d3.axisLeft()\n      .ticks(6)\n      .tickSize(-size * columns.length);\n  return g => g.selectAll(\"g\").data(y).join(\"g\")\n      .attr(\"transform\", (d, i) => `translate(0,${i * size})`)\n      .each(function(d) { return d3.select(this).call(axis.scale(d)); })\n      .call(g => g.select(\".domain\").remove())\n      .call(g => g.selectAll(\".tick line\").attr(\"stroke\", \"#ddd\"));\n}","pinned":true,"mode":"js","data":null,"name":null},{"id":3,"value":"data = d3.csvParse(await FileAttachment(\"penguins.csv\").text(), d3.autoType)","pinned":true,"mode":"js","data":null,"name":null},{"id":6,"value":"columns = data.columns.filter(d => typeof data[0][d] === \"number\")","pinned":true,"mode":"js","data":null,"name":null},{"id":37,"value":"width = 954","pinned":true,"mode":"js","data":null,"name":null},{"id":10,"value":"size = (width - (columns.length + 1) * padding) / columns.length + padding","pinned":true,"mode":"js","data":null,"name":null},{"id":16,"value":"padding = 20","pinned":true,"mode":"js","data":null,"name":null},{"id":26,"value":"d3 = require(\"d3@6\")","pinned":true,"mode":"js","data":null,"name":null},{"id":248,"value":"import {swatches} from \"@d3/color-legend\"","pinned":true,"mode":"js","data":null,"name":null}],"resolutions":[],"schedule":null,"last_view_time":null}