{"id":"3e394e3abf678985","slug":"import","first_public_version":804,"paused_version":null,"likes":132,"publish_level":"live_unlisted","forks":0,"fork_of":null,"has_importers":true,"thumbnail":"dfb202d47ce33609fc3ac602907fe28dcd51d685c43dccb9e39b72fa8dd4f81f","default_thumbnail":"30e53bf49d605c15ecada6a58033575b350cd14bb18e00796d405a2c62dfe6e5","update_time":"2020-06-20T17:48:04.638Z","publish_time":"2023-11-07T19:59:04.529Z","publish_version":806,"latest_version":806,"roles":[],"sharing":null,"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"},"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":[],"files":[{"id":"f1f1344c5afd87a7d173bcfc688ba86da5588220c84ca31d62db89d871459beb953cc9b864301a350744e637e9caff4107edebe51f88687fec6bf96903bde65e","url":"https://static.observableusercontent.com/files/f1f1344c5afd87a7d173bcfc688ba86da5588220c84ca31d62db89d871459beb953cc9b864301a350744e637e9caff4107edebe51f88687fec6bf96903bde65e","download_url":"https://static.observableusercontent.com/files/f1f1344c5afd87a7d173bcfc688ba86da5588220c84ca31d62db89d871459beb953cc9b864301a350744e637e9caff4107edebe51f88687fec6bf96903bde65e?response-content-disposition=attachment%3Bfilename*%3DUTF-8%27%27d3.json","name":"d3.json","create_time":"2019-10-29T23:18:36.658Z","mime_type":"application/json","status":"public","size":21848,"content_encoding":"gzip","private_bucket_id":null},{"id":"73ddce2cd17424eb0f4ea8b4518d0970ce88543512a0bf818f26187a44960ce1b786eabc874e6b565f3bd1f16b5f5a99f4623b145bb38504c9176a8d8c94d165","url":"https://static.observableusercontent.com/files/73ddce2cd17424eb0f4ea8b4518d0970ce88543512a0bf818f26187a44960ce1b786eabc874e6b565f3bd1f16b5f5a99f4623b145bb38504c9176a8d8c94d165","download_url":"https://static.observableusercontent.com/files/73ddce2cd17424eb0f4ea8b4518d0970ce88543512a0bf818f26187a44960ce1b786eabc874e6b565f3bd1f16b5f5a99f4623b145bb38504c9176a8d8c94d165?response-content-disposition=attachment%3Bfilename*%3DUTF-8%27%27cellNamingMarkdown.png","name":"cellNamingMarkdown.png","create_time":"2022-06-07T16:12:00.983Z","mime_type":"image/png","status":"public","size":24514,"content_encoding":null,"private_bucket_id":null}],"comments":[{"id":"cb75c0828cef336e","content":"Where do you go to get the address of a page to import? ie How do I get the \"@name/page\" info?\n\nSeems like an \"Copy Import Link\" in the ellipses menu at top of page would be helpful.","node_id":0,"create_time":"2021-06-15T22:15:22.393Z","update_time":null,"resolved":true,"user":{"id":"bc77e0cd5faff006","avatar_url":"https://avatars.observableusercontent.com/avatar/814a7bacd30761488bdf6c656b5f3b344a25453cf8e845fdffdc2b15c37937b4","login":"kalmdown","name":"kalmdown","bio":"Learning...","home_url":"","tier":"public"}},{"id":"89d17a59374df195","content":"Added an explanation of where to get the address of a page.","node_id":0,"create_time":"2021-07-20T23:47:53.138Z","update_time":null,"resolved":true,"user":{"id":"e32d6575f6e25235","avatar_url":"https://avatars.observableusercontent.com/avatar/d1b0236c0308413347626db6ebcfcddf7584a1e67d1e99546fdcc01e7fd19549","login":"justinting","name":"Justin Ting","bio":"","home_url":"","tier":"pro"}},{"id":"78053a2ed3d518f7","content":"this is out of date since we have pinning now. I am actually looking for documentation on pinning to show a user.... can't find it.","node_id":314,"create_time":"2022-08-11T08:07:57.576Z","update_time":null,"resolved":true,"user":{"id":"5215f6ec4a999d40","avatar_url":"https://avatars.observableusercontent.com/avatar/47327a8bc1966f2186dcb3ebf4b7ee6e4e7ab9a5c2a07405aff57200ea778f71","login":"tomlarkworthy","name":"Tom Larkworthy","bio":"Tech Lead at Taktile. ex Firebase, Google.\n🦋 larkworthy.bsky.social","home_url":"https://bsky.app/profile/larkworthy.bsky.social","tier":"pro"}},{"id":"5f675633434998c7","content":"here is is https://observablehq.com/@observablehq/version-locking-for-notebook-imports","node_id":314,"create_time":"2022-08-11T09:11:56.106Z","update_time":null,"resolved":true,"user":{"id":"5215f6ec4a999d40","avatar_url":"https://avatars.observableusercontent.com/avatar/47327a8bc1966f2186dcb3ebf4b7ee6e4e7ab9a5c2a07405aff57200ea778f71","login":"tomlarkworthy","name":"Tom Larkworthy","bio":"Tech Lead at Taktile. ex Firebase, Google.\n🦋 larkworthy.bsky.social","home_url":"https://bsky.app/profile/larkworthy.bsky.social","tier":"pro"}},{"id":"a0b3a8df67bcfde7","content":"Thanks, Tom--revised paragraph to describe version locking and added link to notebook.","node_id":314,"create_time":"2022-08-11T18:11:47.225Z","update_time":null,"resolved":true,"user":{"id":"e32d6575f6e25235","avatar_url":"https://avatars.observableusercontent.com/avatar/d1b0236c0308413347626db6ebcfcddf7584a1e67d1e99546fdcc01e7fd19549","login":"justinting","name":"Justin Ting","bio":"","home_url":"","tier":"pro"}},{"id":"70b8922e14f167bc","content":"It’s probably worth noting that, now that the D3 examples are importable as functions, there’s no need to use import-with for this use case and that it’s preferable to import the Treemap function and pass the desired data and options. There are likely still some useful applications for import-with but it’s fairly niche.","node_id":317,"create_time":"2021-10-21T05:12:10.552Z","update_time":null,"resolved":false,"user":{"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"}}],"commenting_lock":null,"suggestions_to":[],"suggestion_from":null,"collections":[{"id":"dd678f7d96fe6dd0","type":"public","slug":"notebook-fundamentals","title":"Fundamentals","description":"Understand the fundamental concepts & techniques for working with Observable notebooks.","update_time":"2022-12-12T18:47:24.382Z","pinned":false,"ordered":true,"custom_thumbnail":null,"default_thumbnail":"447d9e47a921a37ee4f7ec90af241b674fc7f26d1195fe225af36d02fe0f9036","thumbnail":"447d9e47a921a37ee4f7ec90af241b674fc7f26d1195fe225af36d02fe0f9036","listing_count":3,"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"}}],"version":733,"title":"Introduction to Imports","license":null,"copyright":"","nodes":[{"id":0,"value":"md`# Introduction to Imports\n\nObservable lets you quickly reuse code by importing named cells from other notebooks.\n\nTo demonstrate, take my notebook of color scales used to visualize quantitative data. This notebook defines a *ramp* function that returns a canvas for a given *color* function, where the *color* function returns a color for a given ${tex`t \\in [0,1]`} from left to right. To create the dreaded angry rainbow, import and call *ramp*:`","pinned":false,"mode":"js","data":null,"name":null},{"id":74,"value":"import {ramp} from \"@mbostock/color-ramp\"","pinned":true,"mode":"js","data":null,"name":null},{"id":77,"value":"ramp(t => `hsl(${t * 360}, 100%, 50%)`)","pinned":true,"mode":"js","data":null,"name":null},{"id":89,"value":"md`You could instead copy-paste the *ramp* function from that notebook into this one—it’s only a few lines of code. But imports have a few advantages.\n\nFirst, the cell you want to reuse may depend on other cells. If you copy-paste, you have to copy-paste those other cells too, and make sure that they don’t conflict with existing cells in your notebook. If you import, the dependencies are loaded automatically, so you don’t have to think about it.\n\nFor example, consider a notebook on an interrupted Sinu–Mollweide map projection that requires the D3 and TopoJSON libraries, along with geometry representing the Earth’s landmasses. You can import the map and display it without worrying about how the sausage is made.`","pinned":false,"mode":"js","data":null,"name":null},{"id":93,"value":"import {map} from \"@d3/interrupted-sinu-mollweide\"","pinned":true,"mode":"js","data":null,"name":null},{"id":97,"value":"map","pinned":true,"mode":"js","data":null,"name":null},{"id":314,"value":"md`Second, by importing, it’s easier to keep the reused code up-to-date. Imports target the latest published version, so if I improve my map notebook, the new map will automatically appear here.\n\n(The risk of importing the latest version is that your notebook may break if the imported notebook changes in a non-backwards-compatible way. Observable also supports versioned imports, however version numbers aren’t currently exposed in the user interface, so for now you must specify the version manually. In the future, imports will be pinned to the latest published version at the time you write the import statement, and can be re-pinned on-demand.)`","pinned":false,"mode":"js","data":null,"name":null},{"id":317,"value":"md`Third, and most excitingly, Observable imports let you inject dependencies using import-with! This lets you “rewrite” imported code as you might do when copy-pasting, without the problems of code duplication.\n\nConsider my D3 treemap notebook that visualizes the Flare dataset. To reuse this treemap implementation to visualize a different dataset, inject a new definition of *data* in the with clause on import:`","pinned":false,"mode":"js","data":null,"name":null},{"id":380,"value":"import {chart as treemap}\nwith {treemap_data as data}\nfrom \"@d3/treemap\"","pinned":true,"mode":"js","data":null,"name":null},{"id":386,"value":"treemap","pinned":true,"mode":"js","data":null,"name":null},{"id":383,"value":"treemap_data = FileAttachment(\"d3.json\").json()","pinned":true,"mode":"js","data":null,"name":null},{"id":389,"value":"md`As long as the new data conforms to the same shape as the old data (here, a tree of objects with *children* and *value* properties), you can reuse the existing code without copy-paste and the terrible challenge of designing a reusable chart abstraction!\n\nBest of all, because [Observable is reactive](/@observablehq/how-observable-runs), you can even inject dynamic definitions into code that was previously static!\n\nFor example, below is a histogram of a synthetic beta distribution using ${tex`\\alpha`} and ${tex`\\beta`} values that are controlled by sliders. Whenever ${tex`\\alpha`} or ${tex`\\beta`} change, Observable automatically updates the histogram—even though the implementation is reused from a static histogram of unemployment rates.`","pinned":false,"mode":"js","data":null,"name":null},{"id":391,"value":"import {chart as histogram}\nwith {histogram_data as data}\nfrom \"@d3/histogram\"","pinned":true,"mode":"js","data":null,"name":null},{"id":697,"value":"viewof alphabeta = {\n  const form = html`<form style=\"font: 12px var(--sans-serif);\">\n  <label style=\"display: block;\">\n    <input name=a type=range min=0 max=5 step=0.01 style=\"width:180px;\">\n    ${tex`\\alpha`} = <output name=oa></output>\n  </label>\n  <label style=\"display: block;\">\n    <input name=b type=range min=0 max=5 step=0.01 style=\"width:180px;\">\n    ${tex`\\beta`} = <output name=ob></output>\n  </label>\n</form>`;\n  form.oninput = () => {\n    form.value = [form.a.valueAsNumber, form.b.valueAsNumber];\n    form.oa.value = form.a.valueAsNumber;\n    form.ob.value = form.b.valueAsNumber;\n  };\n  form.oninput();\n  return form;\n}","pinned":false,"mode":"js","data":null,"name":null},{"id":394,"value":"histogram","pinned":true,"mode":"js","data":null,"name":null},{"id":397,"value":"histogram_data = new Array(10000).fill().map(() => randb(...alphabeta))","pinned":true,"mode":"js","data":null,"name":null},{"id":113,"value":"md`Here are a few additional nuances of imports:\n\nLike [ES imports](http://exploringjs.com/es6/ch_modules.html), Observable imports are live bindings. If you import a value that changes over time ([a generator cell](/@mbostock/generator-cells-functions-and-objects)), such as a countdown to a certain date in the future, the imported value will change over time, too.`","pinned":false,"mode":"js","data":null,"name":null},{"id":115,"value":"import {interval} from \"@mbostock/countdown\"","pinned":true,"mode":"js","data":null,"name":null},{"id":118,"value":"interval","pinned":true,"mode":"js","data":null,"name":null},{"id":3,"value":"md`You can only import named cells, and you must name each cell you want to import explicitly. No anonymous cells allowed; if a notebook uses side effects, as is sometimes common with anonymous cells, you must name and import the cells with side effects, too.\n\nImported cells are lazily evaluated: if you import a cell but you don’t reference it anywhere, the code won’t run. Try uncommenting the cell below and then hit Shift-Enter. Notice that the generator *i* begins at zero—it doesn’t start until referenced.`","pinned":false,"mode":"js","data":null,"name":null},{"id":20,"value":"// i","pinned":true,"mode":"js","data":null,"name":null},{"id":11,"value":"import {i} from \"@mbostock/generator-cells-functions-and-objects\"","pinned":true,"mode":"js","data":null,"name":null},{"id":47,"value":"md`Also like ES imports, only the cells you import are available (*bound*) in the local notebook, even if those cells depend on other cells. Those dependent cells are *run*—they’re just not exposed in the scope of the local notebook.\n\nYou can use circular imports, but only if you don’t use the *with* clause when importing. Import-with effectively creates a local copy of the imported module with your overrides, so a circular import-with would create an infinitely recursive module, a bit like seeing your reflection in a mirrored room.\n\nYou can import the same notebook multiple times and references will resolve exactly. (Note, however, that the versions of the imported notebooks must also match, or they are considered different notebooks.)`","pinned":false,"mode":"js","data":null,"name":null},{"id":561,"value":"import {map as map2} from \"@d3/interrupted-sinu-mollweide\"","pinned":true,"mode":"js","data":null,"name":null},{"id":564,"value":"map === map2","pinned":true,"mode":"js","data":null,"name":null},{"id":718,"value":"md`If you find yourself using lots of imports in your notebooks, you might find our [import visualizer](/@observablehq/notebook-import-visualizer) helpful for debugging. For example, here are the imports used by this notebook. *So meta!*`","pinned":false,"mode":"js","data":null,"name":null},{"id":725,"value":"graph","pinned":false,"mode":"js","data":null,"name":null},{"id":668,"value":"shared = md`### Shared and private imports\n\nYou can import from shared (non-public) notebooks, too:`","pinned":false,"mode":"js","data":null,"name":null},{"id":671,"value":"import {hello} from \"1b9f514e4db7af3a\"","pinned":true,"mode":"js","data":null,"name":null},{"id":674,"value":"hello","pinned":true,"mode":"js","data":null,"name":null},{"id":556,"value":"md`You can even import from private notebooks; however, if you publish a notebook that imports a private notebook, the imported private notebook won’t be accessible to your readers, which may prevent your notebook from functioning as intended. Don’t forget to share or publish your imports before you publish.\n\nLastly, imports aren’t intended to replace libraries (like [lodash](https://lodash.com) or [D3](https://d3js.org)); if you want to design, build and support a reusable library, go for it and publish to npm! But if you want a lightweight way to reuse code across notebooks without resorting to copy-paste, reach for import.\n\n---`","pinned":false,"mode":"js","data":null,"name":null},{"id":430,"value":"md`## Appendix`","pinned":false,"mode":"js","data":null,"name":null},{"id":721,"value":"url = \"https://api.observablehq.com/@observablehq/introduction-to-imports.js?v=3\"","pinned":true,"mode":"js","data":null,"name":null},{"id":728,"value":"orient = \"LR\"","pinned":true,"mode":"js","data":null,"name":null},{"id":724,"value":"import {graphic as graph} with {url, orient} from \"@observablehq/notebook-import-visualizer\"","pinned":true,"mode":"js","data":null,"name":null},{"id":442,"value":"function randb(alpha, beta) {\n  const u = randg(alpha);\n  return u / (u + randg(beta));\n}","pinned":true,"mode":"js","data":null,"name":null},{"id":443,"value":"// Returns a gamma deviate by the method of Marsaglia and Tsang.\nfunction randg(shape) {\n  let oalph = shape, a1, a2, u, v, x, mat;\n  if (!shape) shape = 1;\n  if (shape < 1) shape += 1;\n  a1 = shape - 1 / 3;\n  a2 = 1 / Math.sqrt(9 * a1);\n  do {\n    do {\n      x = randn();\n      v = 1 + a2 * x;\n    } while (v <= 0);\n    v = v * v * v;\n    u = Math.random();\n  } while (\n    u > 1 - 0.331 * Math.pow(x, 4) &&\n    Math.log(u) > 0.5 * x * x + a1 * (1 - v + Math.log(v))\n  );\n  if (shape === oalph) return a1 * v; // alpha > 1\n  do u = Math.random(); while (u === 0); // alpha < 1\n  return Math.pow(u, 1 / oalph) * a1 * v;\n}","pinned":true,"mode":"js","data":null,"name":null},{"id":445,"value":"// Returns a normal deviate (mu=0, sigma=1).\nfunction randn() {\n  let u, v, x, y, q;\n  do {\n    u = Math.random();\n    v = 1.7156 * (Math.random() - 0.5);\n    x = u - 0.449871;\n    y = Math.abs(v) + 0.386595;\n    q = x * x + y * (0.19600 * y - 0.25472 * x);\n  } while (q > 0.27597 && (q > 0.27846 || v * v > -4 * Math.log(u) * u * u));\n  return v / u;\n}","pinned":true,"mode":"js","data":null,"name":null}],"resolutions":[],"schedule":null,"last_view_time":null}