sobhandutta/agentic-personal-assistant
0
1{2 "cells": [3 {4 "cell_type": "markdown",5 "id": "c33b282d",6 "metadata": {},7 "source": [8 "# ๐ง RAG Vector Store โ Deep Dive & Visualisation\n",9 "\n",10 "**How Sobhan's personal knowledge base goes from Markdown files to a searchable vector database.**\n",11 "\n",12 "---\n",13 "\n",14 "## What you will explore in this notebook\n",15 "\n",16 "We have 8 Markdown files about Sobhan Dutta โ his career history, UX/AI expertise, and education.\n",17 "By the end of this notebook you will have:\n",18 "\n",19 "1. Loaded and measured the raw documents (characters, tokens, files)\n",20 "2. Split them into overlapping **chunks** suitable for retrieval\n",21 "3. Converted every chunk into a high-dimensional **embedding vector** using OpenAI\n",22 "4. Stored those vectors in a persistent **ChromaDB** vector store\n",23 "5. **Visualised** the vector space in 2-D and 3-D using t-SNE\n",24 "6. Run live **queries** and watched where they land in vector space\n",25 "\n",26 "---\n",27 "\n",28 "## The three parts\n",29 "\n",30 "| Part | Topic | Key concept |\n",31 "|---|---|---|\n",32 "| **A** | Documents โ Chunks | Why we split and how overlap prevents information loss |\n",33 "| **B** | Chunks โ Vectors | What an embedding is and why dimensions matter |\n",34 "| **C** | Visualise the space | t-SNE, cluster structure, query placement, live retrieval |\n",35 "\n",36 "> **Prerequisites:** `OPENAI_API_KEY` in a `.env` file โ needed for embeddings.\n",37 "> Run `python data/ingest_kb.py` first to build the vector store on disk.\n",38 "> jupyter notebook rag_visualization.ipynb to open notebook"39 ]40 },41 {42 "cell_type": "code",43 "execution_count": 1,44 "id": "35b1f20e",45 "metadata": {},46 "outputs": [47 {48 "name": "stdout",49 "output_type": "stream",50 "text": [51 "OpenAI API Key found โ starts with sk-proj-...\n",52 "\n",53 "Knowledge base : /Users/sobhandutta/projects/full-llm-assistant/knowledge_base\n",54 "Vector store : vector_store\n"55 ]56 }57 ],58 "source": [59 "import os, glob\n",60 "import numpy as np\n",61 "from pathlib import Path\n",62 "from collections import Counter\n",63 "from dotenv import load_dotenv\n",64 "\n",65 "from openai import OpenAI\n",66 "from chromadb import PersistentClient\n",67 "\n",68 "from sklearn.manifold import TSNE\n",69 "import plotly.graph_objects as go\n",70 "\n",71 "import tiktoken\n",72 "\n",73 "load_dotenv(override=True)\n",74 "\n",75 "openai = OpenAI()\n",76 "api_key = os.getenv(\"OPENAI_API_KEY\", \"\")\n",77 "print(f\"OpenAI API Key found โ starts with {api_key[:8]}...\" if api_key else \"โ OPENAI_API_KEY not set\")\n",78 "\n",79 "# Paths โ relative to this notebook (which lives in the agentic/ root)\n",80 "KB_PATH = Path(\"knowledge_base\")\n",81 "VECTOR_STORE_PATH = str(Path(\"vector_store\"))\n",82 "COLLECTION_NAME = \"sobhan_knowledge_base\"\n",83 "EMBEDDING_MODEL = \"text-embedding-3-small\" # must match data/ingest_kb.py\n",84 "\n",85 "print(f\"\\nKnowledge base : {KB_PATH.resolve()}\")\n",86 "print(f\"Vector store : {VECTOR_STORE_PATH}\")"87 ]88 },89 {90 "cell_type": "markdown",91 "id": "f7236d44",92 "metadata": {},93 "source": [94 "---\n",95 "## Part A โ Documents โ Chunks\n",96 "\n",97 "### Step 1: Measure the knowledge base\n",98 "\n",99 "Before touching any LLM, let's measure the raw data:\n",100 "- **Files** โ how many documents we have and how they're organised\n",101 "- **Characters** โ raw size on disk\n",102 "- **Tokens** โ what the LLM *actually* processes (roughly `chars / 4` for English)\n",103 "\n",104 "This tells us: could we just dump everything into one prompt?\n",105 "Spoiler: technically yes for 8 files โ but RAG is still the right pattern for accuracy, cost, and scalability."106 ]107 },108 {109 "cell_type": "code",110 "execution_count": 2,111 "id": "388fbfc6",112 "metadata": {},113 "outputs": [114 {115 "name": "stdout",116 "output_type": "stream",117 "text": [118 "==================================================\n",119 " Knowledge Base: 9 documents\n",120 "==================================================\n",121 "\n",122 " ๐ career/ (4 files)\n",123 " โโ ataya.md\n",124 " โโ early_career.md\n",125 " โโ elisity.md\n",126 " โโ nuance.md\n",127 "\n",128 " ๐ education/ (1 files)\n",129 " โโ background.md\n",130 "\n",131 " ๐ expertise/ (3 files)\n",132 " โโ frontend_engineering.md\n",133 " โโ leadership.md\n",134 " โโ ux_design_philosophy.md\n",135 "\n",136 " ๐ youtube/ (1 files)\n",137 " โโ youtube.md\n",138 "\n",139 "==================================================\n",140 " Total characters : 25,400\n",141 " Average per file : 2,822 chars\n"142 ]143 }144 ],145 "source": [146 "# Load every .md file and gather stats\n",147 "documents = []\n",148 "for md_file in sorted(KB_PATH.rglob(\"*.md\")):\n",149 " category = md_file.parent.name # \"career\", \"expertise\", or \"education\"\n",150 " text = md_file.read_text(encoding=\"utf-8\")\n",151 " documents.append({\"path\": md_file, \"category\": category,\n",152 " \"filename\": md_file.stem, \"text\": text})\n",153 "\n",154 "# Print a summary tree\n",155 "print(f\"{'='*50}\")\n",156 "print(f\" Knowledge Base: {len(documents)} documents\")\n",157 "print(f\"{'='*50}\")\n",158 "category_counts = Counter(d[\"category\"] for d in documents)\n",159 "for cat, count in sorted(category_counts.items()):\n",160 " cat_docs = [d[\"filename\"] for d in documents if d[\"category\"] == cat]\n",161 " print(f\"\\n ๐ {cat}/ ({count} files)\")\n",162 " for name in cat_docs:\n",163 " print(f\" โโ {name}.md\")\n",164 "\n",165 "# Measure total size\n",166 "all_text = \"\\n\\n\".join(d[\"text\"] for d in documents)\n",167 "print(f\"\\n{'='*50}\")\n",168 "print(f\" Total characters : {len(all_text):>8,}\")\n",169 "print(f\" Average per file : {len(all_text)/len(documents):>8,.0f} chars\")"170 ]171 },172 {173 "cell_type": "code",174 "execution_count": 3,175 "id": "57f9405b",176 "metadata": {},177 "outputs": [178 {179 "name": "stdout",180 "output_type": "stream",181 "text": [182 "Total tokens in knowledge base : 5,230\n",183 "Chars-per-token ratio : 4.86 (English โ 4)\n",184 "\n",185 "Fits in Claude context (200,000 tokens)? โ
YES\n",186 "Fits in GPT-4o context (128,000 tokens)? โ
YES\n",187 "\n",188 "Cost to send ALL docs every query : ~$0.0000 (haiku input pricing)\n",189 "Cost with RAG (top-5 chunks) : ~$0.000002 (7ร cheaper)\n"190 ]191 }192 ],193 "source": [194 "# Token count using the tokeniser claude-sonnet-4-6 / gpt-4o share (cl100k_base)\n",195 "encoding = tiktoken.get_encoding(\"cl100k_base\")\n",196 "token_count = len(encoding.encode(all_text))\n",197 "\n",198 "print(f\"Total tokens in knowledge base : {token_count:,}\")\n",199 "print(f\"Chars-per-token ratio : {len(all_text)/token_count:.2f} (English โ 4)\")\n",200 "\n",201 "# Cost and context comparison\n",202 "claude_context = 200_000\n",203 "gpt4_context = 128_000\n",204 "cost_full = token_count / 1_000_000 * 0.003 # claude-haiku input price\n",205 "\n",206 "print(f\"\\nFits in Claude context ({claude_context:,} tokens)? \"\n",207 " + (\"โ
YES\" if token_count < claude_context else \"โ NO\"))\n",208 "print(f\"Fits in GPT-4o context ({gpt4_context:,} tokens)? \"\n",209 " + (\"โ
YES\" if token_count < gpt4_context else \"โ NO\"))\n",210 "print(f\"\\nCost to send ALL docs every query : ~${cost_full:.4f} (haiku input pricing)\")\n",211 "\n",212 "rag_tokens = 5 * 150 # top-5 chunks ร ~150 tokens each\n",213 "cost_rag = rag_tokens / 1_000_000 * 0.003\n",214 "print(f\"Cost with RAG (top-5 chunks) : ~${cost_rag:.6f} ({cost_full/cost_rag:.0f}ร cheaper)\")"215 ]216 },217 {218 "cell_type": "markdown",219 "id": "a874e332",220 "metadata": {},221 "source": [222 "### Step 2: Split documents into chunks\n",223 "\n",224 "**Why chunk at all?**\n",225 "\n",226 "Each document becomes a single vector if stored whole. A long document about Sobhan's career\n",227 "would produce one vector that \"averages\" all its content โ making it hard to match a specific\n",228 "question like \"What did Sobhan achieve at Elisity?\".\n",229 "\n",230 "Smaller, focused chunks = more precise retrieval.\n",231 "\n",232 "**Why overlap?**\n",233 "\n",234 "```\n",235 "Doc text: |โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ|\n",236 "\n",237 "Chunk 1: |โโโโโโ 600 โโโโโโ|\n",238 "Chunk 2: |โโ 100 overlap โโ|โโโโโโ 600 โโโโโโ|\n",239 "Chunk 3: |โโ 100 โโ|โโ 600 โโ|\n",240 "```\n",241 "\n",242 "A sentence near a chunk boundary appears in **two** chunks. Without overlap, it might be\n",243 "split and never fully retrieved. Overlap trades a little storage for significantly better retrieval."244 ]245 },246 {247 "cell_type": "code",248 "execution_count": 4,249 "id": "8b97f3e9",250 "metadata": {},251 "outputs": [252 {253 "name": "stdout",254 "output_type": "stream",255 "text": [256 "Documents : 9\n",257 "Chunks : 53\n",258 "Avg per doc : 5.9\n",259 "\n",260 "Chunk 0 preview (600 chars):\n",261 " Source : ataya (career)\n",262 " Content : # Director of Engineering (UX & UI) โ Atayalan Inc\n",263 "\n",264 "## Role Overview\n",265 "Sobhan Dutta joined Atayalan Inc in January 2022 as the founding U.S. hire and Director of Engineering (UX & UI), based in San Jose...\n"266 ]267 }268 ],269 "source": [270 "CHUNK_SIZE = 600 # characters per chunk (matches data/ingest_kb.py)\n",271 "CHUNK_OVERLAP = 100 # overlap between adjacent chunks\n",272 "\n",273 "def chunk_text(text, chunk_size, overlap):\n",274 " chunks, start = [], 0\n",275 " while start < len(text):\n",276 " chunks.append(text[start : start + chunk_size])\n",277 " start += chunk_size - overlap\n",278 " return chunks\n",279 "\n",280 "# Chunk every document and attach metadata\n",281 "all_chunks = []\n",282 "for doc in documents:\n",283 " for i, chunk_str in enumerate(chunk_text(doc[\"text\"], CHUNK_SIZE, CHUNK_OVERLAP)):\n",284 " if len(chunk_str.strip()) >= 50: # skip tiny trailing fragments\n",285 " all_chunks.append({\n",286 " \"text\": chunk_str,\n",287 " \"source\": doc[\"filename\"],\n",288 " \"category\": doc[\"category\"],\n",289 " \"chunk_index\": i,\n",290 " })\n",291 "\n",292 "print(f\"Documents : {len(documents)}\")\n",293 "print(f\"Chunks : {len(all_chunks)}\")\n",294 "print(f\"Avg per doc : {len(all_chunks)/len(documents):.1f}\")\n",295 "print(f\"\\nChunk 0 preview ({len(all_chunks[0]['text'])} chars):\")\n",296 "print(f\" Source : {all_chunks[0]['source']} ({all_chunks[0]['category']})\")\n",297 "print(f\" Content : {all_chunks[0]['text'][:200].strip()}...\")"298 ]299 },300 {301 "cell_type": "code",302 "execution_count": 5,303 "id": "3e975c16",304 "metadata": {},305 "outputs": [306 {307 "data": {308 "application/vnd.plotly.v1+json": {309 "config": {310 "plotlyServerURL": "https://plot.ly"311 },312 "data": [313 {314 "marker": {315 "color": "#6366f1"316 },317 "name": "Chunk size",318 "nbinsx": 30,319 "opacity": 0.85,320 "type": "histogram",321 "x": [322 600,323 600,324 600,325 600,326 389,327 600,328 600,329 600,330 550,331 600,332 600,333 600,334 600,335 320,336 600,337 600,338 600,339 600,340 600,341 423,342 600,343 600,344 600,345 600,346 431,347 600,348 600,349 600,350 600,351 600,352 128,353 600,354 600,355 600,356 600,357 600,358 384,359 600,360 600,361 600,362 600,363 600,364 257,365 600,366 600,367 600,368 600,369 600,370 600,371 600,372 600,373 600,374 502375 ]376 }377 ],378 "layout": {379 "annotations": [380 {381 "showarrow": false,382 "text": " Mean: 562 chars",383 "x": 561.9622641509434,384 "xanchor": "left",385 "xref": "x",386 "y": 1,387 "yanchor": "top",388 "yref": "y domain"389 }390 ],391 "height": 400,392 "shapes": [393 {394 "line": {395 "color": "orange",396 "dash": "dash"397 },398 "type": "line",399 "x0": 561.9622641509434,400 "x1": 561.9622641509434,401 "xref": "x",402 "y0": 0,403 "y1": 1,404 "yref": "y domain"405 }406 ],407 "template": {408 "data": {409 "bar": [410 {411 "error_x": {412 "color": "#f2f5fa"413 },414 "error_y": {415 "color": "#f2f5fa"416 },417 "marker": {418 "line": {419 "color": "rgb(17,17,17)",420 "width": 0.5421 },422 "pattern": {423 "fillmode": "overlay",424 "size": 10,425 "solidity": 0.2426 }427 },428 "type": "bar"429 }430 ],431 "barpolar": [432 {433 "marker": {434 "line": {435 "color": "rgb(17,17,17)",436 "width": 0.5437 },438 "pattern": {439 "fillmode": "overlay",440 "size": 10,441 "solidity": 0.2442 }443 },444 "type": "barpolar"445 }446 ],447 "carpet": [448 {449 "aaxis": {450 "endlinecolor": "#A2B1C6",451 "gridcolor": "#506784",452 "linecolor": "#506784",453 "minorgridcolor": "#506784",454 "startlinecolor": "#A2B1C6"455 },456 "baxis": {457 "endlinecolor": "#A2B1C6",458 "gridcolor": "#506784",459 "linecolor": "#506784",460 "minorgridcolor": "#506784",461 "startlinecolor": "#A2B1C6"462 },463 "type": "carpet"464 }465 ],466 "choropleth": [467 {468 "colorbar": {469 "outlinewidth": 0,470 "ticks": ""471 },472 "type": "choropleth"473 }474 ],475 "contour": [476 {477 "colorbar": {478 "outlinewidth": 0,479 "ticks": ""480 },481 "colorscale": [482 [483 0,484 "#0d0887"485 ],486 [487 0.1111111111111111,488 "#46039f"489 ],490 [491 0.2222222222222222,492 "#7201a8"493 ],494 [495 0.3333333333333333,496 "#9c179e"497 ],498 [499 0.4444444444444444,500 "#bd3786"501 ],502 [503 0.5555555555555556,504 "#d8576b"505 ],506 [507 0.6666666666666666,508 "#ed7953"509 ],510 [511 0.7777777777777778,512 "#fb9f3a"513 ],514 [515 0.8888888888888888,516 "#fdca26"517 ],518 [519 1,520 "#f0f921"521 ]522 ],523 "type": "contour"524 }525 ],526 "contourcarpet": [527 {528 "colorbar": {529 "outlinewidth": 0,530 "ticks": ""531 },532 "type": "contourcarpet"533 }534 ],535 "heatmap": [536 {537 "colorbar": {538 "outlinewidth": 0,539 "ticks": ""540 },541 "colorscale": [542 [543 0,544 "#0d0887"545 ],546 [547 0.1111111111111111,548 "#46039f"549 ],550 [551 0.2222222222222222,552 "#7201a8"553 ],554 [555 0.3333333333333333,556 "#9c179e"557 ],558 [559 0.4444444444444444,560 "#bd3786"561 ],562 [563 0.5555555555555556,564 "#d8576b"565 ],566 [567 0.6666666666666666,568 "#ed7953"569 ],570 [571 0.7777777777777778,572 "#fb9f3a"573 ],574 [575 0.8888888888888888,576 "#fdca26"577 ],578 [579 1,580 "#f0f921"581 ]582 ],583 "type": "heatmap"584 }585 ],586 "histogram": [587 {588 "marker": {589 "pattern": {590 "fillmode": "overlay",591 "size": 10,592 "solidity": 0.2593 }594 },595 "type": "histogram"596 }597 ],598 "histogram2d": [599 {600 "colorbar": {601 "outlinewidth": 0,602 "ticks": ""603 },604 "colorscale": [605 [606 0,607 "#0d0887"608 ],609 [610 0.1111111111111111,611 "#46039f"612 ],613 [614 0.2222222222222222,615 "#7201a8"616 ],617 [618 0.3333333333333333,619 "#9c179e"620 ],621 [622 0.4444444444444444,623 "#bd3786"624 ],625 [626 0.5555555555555556,627 "#d8576b"628 ],629 [630 0.6666666666666666,631 "#ed7953"632 ],633 [634 0.7777777777777778,635 "#fb9f3a"636 ],637 [638 0.8888888888888888,639 "#fdca26"640 ],641 [642 1,643 "#f0f921"644 ]645 ],646 "type": "histogram2d"647 }648 ],649 "histogram2dcontour": [650 {651 "colorbar": {652 "outlinewidth": 0,653 "ticks": ""654 },655 "colorscale": [656 [657 0,658 "#0d0887"659 ],660 [661 0.1111111111111111,662 "#46039f"663 ],664 [665 0.2222222222222222,666 "#7201a8"667 ],668 [669 0.3333333333333333,670 "#9c179e"671 ],672 [673 0.4444444444444444,674 "#bd3786"675 ],676 [677 0.5555555555555556,678 "#d8576b"679 ],680 [681 0.6666666666666666,682 "#ed7953"683 ],684 [685 0.7777777777777778,686 "#fb9f3a"687 ],688 [689 0.8888888888888888,690 "#fdca26"691 ],692 [693 1,694 "#f0f921"695 ]696 ],697 "type": "histogram2dcontour"698 }699 ],700 "mesh3d": [701 {702 "colorbar": {703 "outlinewidth": 0,704 "ticks": ""705 },706 "type": "mesh3d"707 }708 ],709 "parcoords": [710 {711 "line": {712 "colorbar": {713 "outlinewidth": 0,714 "ticks": ""715 }716 },717 "type": "parcoords"718 }719 ],720 "pie": [721 {722 "automargin": true,723 "type": "pie"724 }725 ],726 "scatter": [727 {728 "marker": {729 "line": {730 "color": "#283442"731 }732 },733 "type": "scatter"734 }735 ],736 "scatter3d": [737 {738 "line": {739 "colorbar": {740 "outlinewidth": 0,741 "ticks": ""742 }743 },744 "marker": {745 "colorbar": {746 "outlinewidth": 0,747 "ticks": ""748 }749 },750 "type": "scatter3d"751 }752 ],753 "scattercarpet": [754 {755 "marker": {756 "colorbar": {757 "outlinewidth": 0,758 "ticks": ""759 }760 },761 "type": "scattercarpet"762 }763 ],764 "scattergeo": [765 {766 "marker": {767 "colorbar": {768 "outlinewidth": 0,769 "ticks": ""770 }771 },772 "type": "scattergeo"773 }774 ],775 "scattergl": [776 {777 "marker": {778 "line": {779 "color": "#283442"780 }781 },782 "type": "scattergl"783 }784 ],785 "scattermap": [786 {787 "marker": {788 "colorbar": {789 "outlinewidth": 0,790 "ticks": ""791 }792 },793 "type": "scattermap"794 }795 ],796 "scattermapbox": [797 {798 "marker": {799 "colorbar": {800 "outlinewidth": 0,801 "ticks": ""802 }803 },804 "type": "scattermapbox"805 }806 ],807 "scatterpolar": [808 {809 "marker": {810 "colorbar": {811 "outlinewidth": 0,812 "ticks": ""813 }814 },815 "type": "scatterpolar"816 }817 ],818 "scatterpolargl": [819 {820 "marker": {821 "colorbar": {822 "outlinewidth": 0,823 "ticks": ""824 }825 },826 "type": "scatterpolargl"827 }828 ],829 "scatterternary": [830 {831 "marker": {832 "colorbar": {833 "outlinewidth": 0,834 "ticks": ""835 }836 },837 "type": "scatterternary"838 }839 ],840 "surface": [841 {842 "colorbar": {843 "outlinewidth": 0,844 "ticks": ""845 },846 "colorscale": [847 [848 0,849 "#0d0887"850 ],851 [852 0.1111111111111111,853 "#46039f"854 ],855 [856 0.2222222222222222,857 "#7201a8"858 ],859 [860 0.3333333333333333,861 "#9c179e"862 ],863 [864 0.4444444444444444,865 "#bd3786"866 ],867 [868 0.5555555555555556,869 "#d8576b"870 ],871 [872 0.6666666666666666,873 "#ed7953"874 ],875 [876 0.7777777777777778,877 "#fb9f3a"878 ],879 [880 0.8888888888888888,881 "#fdca26"882 ],883 [884 1,885 "#f0f921"886 ]887 ],888 "type": "surface"889 }890 ],891 "table": [892 {893 "cells": {894 "fill": {895 "color": "#506784"896 },897 "line": {898 "color": "rgb(17,17,17)"899 }900 },901 "header": {902 "fill": {903 "color": "#2a3f5f"904 },905 "line": {906 "color": "rgb(17,17,17)"907 }908 },909 "type": "table"910 }911 ]912 },913 "layout": {914 "annotationdefaults": {915 "arrowcolor": "#f2f5fa",916 "arrowhead": 0,917 "arrowwidth": 1918 },919 "autotypenumbers": "strict",920 "coloraxis": {921 "colorbar": {922 "outlinewidth": 0,923 "ticks": ""924 }925 },926 "colorscale": {927 "diverging": [928 [929 0,930 "#8e0152"931 ],932 [933 0.1,934 "#c51b7d"935 ],936 [937 0.2,938 "#de77ae"939 ],940 [941 0.3,942 "#f1b6da"943 ],944 [945 0.4,946 "#fde0ef"947 ],948 [949 0.5,950 "#f7f7f7"951 ],952 [953 0.6,954 "#e6f5d0"955 ],956 [957 0.7,958 "#b8e186"959 ],960 [961 0.8,962 "#7fbc41"963 ],964 [965 0.9,966 "#4d9221"967 ],968 [969 1,970 "#276419"971 ]972 ],973 "sequential": [974 [975 0,976 "#0d0887"977 ],978 [979 0.1111111111111111,980 "#46039f"981 ],982 [983 0.2222222222222222,984 "#7201a8"985 ],986 [987 0.3333333333333333,988 "#9c179e"989 ],990 [991 0.4444444444444444,992 "#bd3786"993 ],994 [995 0.5555555555555556,996 "#d8576b"997 ],998 [999 0.6666666666666666,1000 "#ed7953"1001 ],1002 [1003 0.7777777777777778,1004 "#fb9f3a"1005 ],1006 [1007 0.8888888888888888,1008 "#fdca26"1009 ],1010 [1011 1,1012 "#f0f921"1013 ]1014 ],1015 "sequentialminus": [1016 [1017 0,1018 "#0d0887"1019 ],1020 [1021 0.1111111111111111,1022 "#46039f"1023 ],1024 [1025 0.2222222222222222,1026 "#7201a8"1027 ],1028 [1029 0.3333333333333333,1030 "#9c179e"1031 ],1032 [1033 0.4444444444444444,1034 "#bd3786"1035 ],1036 [1037 0.5555555555555556,1038 "#d8576b"1039 ],1040 [1041 0.6666666666666666,1042 "#ed7953"1043 ],1044 [1045 0.7777777777777778,1046 "#fb9f3a"1047 ],1048 [1049 0.8888888888888888,1050 "#fdca26"1051 ],1052 [1053 1,1054 "#f0f921"1055 ]1056 ]1057 },1058 "colorway": [1059 "#636efa",1060 "#EF553B",1061 "#00cc96",1062 "#ab63fa",1063 "#FFA15A",1064 "#19d3f3",1065 "#FF6692",1066 "#B6E880",1067 "#FF97FF",1068 "#FECB52"1069 ],1070 "font": {1071 "color": "#f2f5fa"1072 },1073 "geo": {1074 "bgcolor": "rgb(17,17,17)",1075 "lakecolor": "rgb(17,17,17)",1076 "landcolor": "rgb(17,17,17)",1077 "showlakes": true,1078 "showland": true,1079 "subunitcolor": "#506784"1080 },1081 "hoverlabel": {1082 "align": "left"1083 },1084 "hovermode": "closest",1085 "mapbox": {1086 "style": "dark"1087 },1088 "paper_bgcolor": "rgb(17,17,17)",1089 "plot_bgcolor": "rgb(17,17,17)",1090 "polar": {1091 "angularaxis": {1092 "gridcolor": "#506784",1093 "linecolor": "#506784",1094 "ticks": ""1095 },1096 "bgcolor": "rgb(17,17,17)",1097 "radialaxis": {1098 "gridcolor": "#506784",1099 "linecolor": "#506784",1100 "ticks": ""1101 }1102 },1103 "scene": {1104 "xaxis": {1105 "backgroundcolor": "rgb(17,17,17)",1106 "gridcolor": "#506784",1107 "gridwidth": 2,1108 "linecolor": "#506784",1109 "showbackground": true,1110 "ticks": "",1111 "zerolinecolor": "#C8D4E3"1112 },1113 "yaxis": {1114 "backgroundcolor": "rgb(17,17,17)",1115 "gridcolor": "#506784",1116 "gridwidth": 2,1117 "linecolor": "#506784",1118 "showbackground": true,1119 "ticks": "",1120 "zerolinecolor": "#C8D4E3"1121 },1122 "zaxis": {1123 "backgroundcolor": "rgb(17,17,17)",1124 "gridcolor": "#506784",1125 "gridwidth": 2,1126 "linecolor": "#506784",1127 "showbackground": true,1128 "ticks": "",1129 "zerolinecolor": "#C8D4E3"1130 }1131 },1132 "shapedefaults": {1133 "line": {1134 "color": "#f2f5fa"1135 }1136 },1137 "sliderdefaults": {1138 "bgcolor": "#C8D4E3",1139 "bordercolor": "rgb(17,17,17)",1140 "borderwidth": 1,1141 "tickwidth": 01142 },1143 "ternary": {1144 "aaxis": {1145 "gridcolor": "#506784",1146 "linecolor": "#506784",1147 "ticks": ""1148 },1149 "baxis": {1150 "gridcolor": "#506784",1151 "linecolor": "#506784",1152 "ticks": ""1153 },1154 "bgcolor": "rgb(17,17,17)",1155 "caxis": {1156 "gridcolor": "#506784",1157 "linecolor": "#506784",1158 "ticks": ""1159 }1160 },1161 "title": {1162 "x": 0.051163 },1164 "updatemenudefaults": {1165 "bgcolor": "#506784",1166 "borderwidth": 01167 },1168 "xaxis": {1169 "automargin": true,1170 "gridcolor": "#283442",1171 "linecolor": "#506784",1172 "ticks": "",1173 "title": {1174 "standoff": 151175 },1176 "zerolinecolor": "#283442",1177 "zerolinewidth": 21178 },1179 "yaxis": {1180 "automargin": true,1181 "gridcolor": "#283442",1182 "linecolor": "#506784",1183 "ticks": "",1184 "title": {1185 "standoff": 151186 },1187 "zerolinecolor": "#283442",1188 "zerolinewidth": 21189 }1190 }1191 },1192 "title": {1193 "text": "Distribution of Chunk Sizes (characters)"1194 },1195 "width": 750,1196 "xaxis": {1197 "title": {1198 "text": "Characters per chunk"1199 }1200 },