{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "%load_ext autoreload\n",
    "%autoreload 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "import os\n",
    "import numpy as np\n",
    "from Firefly.data_reader import ParticleGroup,Settings,ArrayReader"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Managing Custom Settings\n",
    "One of the core features of Firefly is the ability to customize the user interface (UI) and the startup behavior to make bespoke iterations of Firefly using ones own data. We have organized the different options that one can customize into different settings groups that fall into two categories: those that affect the app as a whole and those that are particular to an individual group of particles.\n",
    "\n",
    "**App Settings** |     |**Particle Group Settings**\n",
    ":-----:|:--:|:------:\n",
    "Startup|         |Startup\n",
    "UI|              |UI\n",
    "Window|          |Filter\n",
    "Camera|          |Colormap\n",
    "\n",
    "\n",
    "\n",
    "Specific information for each key can be found in <a href=\"https://ageller.github.io/Firefly/docs/build/html/data_reader/settings.html\">this documentation</a>.  \n",
    "\n",
    "To create the necessary JSON files one should use the `Firefly.data_reader.Settings` class to create a `Settings` object. Once you have a `Settings` object you can manipulate the settings as you see fit and then either \n",
    "1. manually save it to a file using the `outputToJSON()` method or\n",
    "2. connect it to a `Firefly.data_reader.Reader` object in order to link it to a specific visualization (see the <a href=\"https://ageller.github.io/Firefly/docs/build/html/data_reader/reader.html\">reader documentation</a> for details on how to use a `Reader` object)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--  Settings: startup settings --\n",
      "['maxVrange', 'startFly', 'friction', 'stereo', 'stereoSep', 'decimate']\n",
      "\n",
      "--  Settings: UI settings --\n",
      "['UI', 'UIfullscreen', 'UIsnapshot', 'UIreset', 'UIsavePreset', 'UIloadNewData', 'UIcameraControls', 'UIdecimation']\n",
      "\n",
      "--  Settings: window settings --\n",
      "['title', 'loaded', 'annotation']\n",
      "\n",
      "--  Settings: camera settings --\n",
      "['center', 'camera', 'cameraRotation']\n",
      "\n",
      "--  Settings: particle startup settings --\n",
      "['plotNmax', 'showVel', 'velType', 'color', 'sizeMult', 'showParts']\n",
      "\n",
      "--  Settings: particle UI settings --\n",
      "['UIparticle', 'UIdropdown', 'UIcolorPicker']\n",
      "\n",
      "--  Settings: particle filter settings --\n",
      "['filterLims', 'filterVals']\n",
      "\n",
      "--  Settings: particle colormap settings --\n",
      "['colormapLims', 'colormapVals', 'colormap', 'colormapVariable', 'showColormap']\n",
      "\n"
     ]
    }
   ],
   "source": [
    "## let's create an settings object with the default keys\n",
    "settings = Settings()\n",
    "\n",
    "## we'll print the current settings to the console, organized into groups \n",
    "##  (but we'll use the values=False keyword argument because we only want to see the keys for now)\n",
    "settings.printKeys(values=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Settings can be changed the same way you would change a key in a dictionary\n",
    "There is key validation (so you can't attempt to set a setting that doesn't exist) but there is no value validation, so be careful that you use appropriate values or your app might not work. See the <a href=\"https://ageller.github.io/Firefly/docs/build/html/data_reader/settings.html\">settings documentation</a> for details on what values each setting can take."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "before:\n",
      "--  Settings: window settings --\n",
      "title Firefly\n",
      "loaded True\n",
      "annotation None\n",
      "\n",
      "after:\n",
      "--  Settings: window settings --\n",
      "title ---> My Favorite Data <--- \n",
      "loaded True\n",
      "annotation None\n",
      "\n"
     ]
    }
   ],
   "source": [
    "## let's change the title that shows up in the browser's tab list\n",
    "print(\"before:\")\n",
    "## print only the settings that have to do with the window\n",
    "settings.printKeys(pattern='window')\n",
    "## update the title using dictionary syntax\n",
    "settings['title']='---> My Favorite Data <--- '\n",
    "print(\"after:\")\n",
    "## print only the settings that have to do with the window to confirm it changed\n",
    "settings.printKeys(pattern='window')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Settings are most useful when connected to a `Firefly.data_reader.Reader` object\n",
    "Doing so allows many of the necessary settings to be automatically generated as additional particle groups are added."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "## let's create some sample data, a grid of points in a 3d cube\n",
    "my_coords = np.linspace(-10,10,20)\n",
    "xs,ys,zs = np.meshgrid(my_coords,my_coords,my_coords)\n",
    "xs,ys,zs = xs.flatten(),ys.flatten(),zs.flatten()\n",
    "coords = np.array([xs,ys,zs]).T\n",
    "\n",
    "## we'll pick some random field values to demonstrate filtering/colormapping\n",
    "fields = np.random.random(size=xs.size)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Before we've attached the `Settings` object the particle settings are all empty."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "--  Settings: particle startup settings --\n",
      "plotNmax {}\n",
      "showVel {}\n",
      "velType {}\n",
      "color {}\n",
      "sizeMult {}\n",
      "showParts {}\n",
      "\n",
      "--  Settings: particle UI settings --\n",
      "UIparticle {}\n",
      "UIdropdown {}\n",
      "UIcolorPicker {}\n",
      "\n",
      "--  Settings: particle filter settings --\n",
      "filterLims {}\n",
      "filterVals {}\n",
      "\n",
      "--  Settings: particle colormap settings --\n",
      "colormapLims {}\n",
      "colormapVals {}\n",
      "colormap {}\n",
      "colormapVariable {}\n",
      "showColormap {}\n",
      "\n"
     ]
    }
   ],
   "source": [
    "\n",
    "settings.printKeys(pattern='particle')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We'll use a `Firefly.data_reader.ArrayReader`, a workhorse `Firefly.data_reader.Reader` sub-class with many convenient functions. See the <a href=\"https://ageller.github.io/Firefly/docs/build/html/data_reader/reader.html\">reader documentation</a> for details that are outside the scope of this tutorial."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "JSONdir is None, defaulting to /Users/agurvich/research/repos/Firefly/src/Firefly/static/data/Data\n",
      "Make sure each tracked_array (2) has a tracked_filter_flag (0), assuming True.\n",
      "Make sure each tracked_array (2) has a tracked_colormap_flag (0), assuming True.\n",
      "Outputting: PGroup_0 - 7999/7999 particles - 0 tracked fields\n",
      "Outputting: PGroup_1 - 8000/8000 particles - 2 tracked fields\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/Users/agurvich/miniconda3/envs/pypi_test/lib/python3.9/site-packages/numpy-1.21.0-py3.9-macosx-10.9-x86_64.egg/numpy/core/fromnumeric.py:1970: VisibleDeprecationWarning: Creating an ndarray from ragged nested sequences (which is a list-or-tuple of lists-or-tuples-or ndarrays with different lengths or shapes) is deprecated. If you meant to do this, you must specify 'dtype=object' when creating the ndarray.\n",
      "  result = asarray(a).shape\n",
      "/Users/agurvich/miniconda3/envs/pypi_test/lib/python3.9/site-packages/numpy-1.21.0-py3.9-macosx-10.9-x86_64.egg/numpy/core/fromnumeric.py:43: VisibleDeprecationWarning: Creating an ndarray from ragged nested sequences (which is a list-or-tuple of lists-or-tuples-or ndarrays with different lengths or shapes) is deprecated. If you meant to do this, you must specify 'dtype=object' when creating the ndarray.\n",
      "  result = getattr(asarray(obj), method)(*args, **kwds)\n"
     ]
    }
   ],
   "source": [
    "## initialize an ArrayReader\n",
    "reader = ArrayReader(\n",
    "    coordinates=[coords[:-1],coords], ## pass in two particle groups as a demonstration (just copies of our sample data)\n",
    "    write_jsons_to_disk=False,\n",
    "    settings=settings, ## the settings object to link\n",
    "    fields=[[],[fields,fields]]) ## field data for each particle group, 0 fields for 1 and 2 repeated fields for the other."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The original `Settings` object is stored in `reader.settings`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(reader.settings is settings) = True\n",
      "\n",
      "--  Settings: particle startup settings --\n",
      "plotNmax {'PGroup_0': None, 'PGroup_1': None}\n",
      "showVel {'PGroup_0': False, 'PGroup_1': False}\n",
      "velType {'PGroup_0': None, 'PGroup_1': None}\n",
      "color {'PGroup_0': array([0.82714818, 0.91926459, 0.16732225, 1.        ]), 'PGroup_1': array([0.5381936 , 0.13206462, 0.37324155, 1.        ])}\n",
      "sizeMult {'PGroup_0': 1.0, 'PGroup_1': 1.0}\n",
      "showParts {'PGroup_0': True, 'PGroup_1': True}\n",
      "\n",
      "--  Settings: particle UI settings --\n",
      "UIparticle {'PGroup_0': True, 'PGroup_1': True}\n",
      "UIdropdown {'PGroup_0': True, 'PGroup_1': True}\n",
      "UIcolorPicker {'PGroup_0': True, 'PGroup_1': True}\n",
      "\n",
      "--  Settings: particle filter settings --\n",
      "filterLims {'PGroup_0': {}, 'PGroup_1': {'field0': None, 'field1': None}}\n",
      "filterVals {'PGroup_0': {}, 'PGroup_1': {'field0': None, 'field1': None}}\n",
      "\n",
      "--  Settings: particle colormap settings --\n",
      "colormapLims {'PGroup_0': {}, 'PGroup_1': {'field0': None, 'field1': None}}\n",
      "colormapVals {'PGroup_0': {}, 'PGroup_1': {'field0': None, 'field1': None}}\n",
      "colormap {'PGroup_0': 0.015625, 'PGroup_1': 0.015625}\n",
      "colormapVariable {'PGroup_0': 0, 'PGroup_1': 0}\n",
      "showColormap {'PGroup_0': False, 'PGroup_1': False}\n",
      "\n"
     ]
    }
   ],
   "source": [
    "## demonstrate that reader.settings is the original settings object\n",
    "print('(reader.settings is settings) =',reader.settings is settings)\n",
    "print()\n",
    "\n",
    "reader.settings.printKeys(pattern='particle')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Notice that the dictionaries are filled with keys corresponding to each of the particle groups we passed in and sensible default values for each. The values of nested dictionaries should be changed by accessing each in turn, e.g.\n",
    "```python\n",
    "settings['colormapLims']['PGroup_1']['field0'] = [0,1]\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "for the purposes of this tutorial, we'll just go ahead and output the `Settings` object we have manually"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "You will need to add this settings filename to filenames.json if this was not called by a Reader instance.\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "('./exampleSettings.json', './exampleSettings.json')"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "## output the example settings file to a .json in this directory\n",
    "settings.outputToJSON('.','example')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Settings can also be imported from `.json` files\n",
    "Only settings defined in the file will be overwritten, so you can also mix-and-match settings files."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "replacing title\n",
      "Firefly --> ---> My Favorite Data <--- \n",
      "replacing plotNmax\n",
      "{} --> {'PGroup_0': None, 'PGroup_1': None}\n",
      "replacing showVel\n",
      "{} --> {'PGroup_0': False, 'PGroup_1': False}\n",
      "replacing velType\n",
      "{} --> {'PGroup_0': None, 'PGroup_1': None}\n",
      "replacing color\n",
      "{} --> {'PGroup_0': [0.047194449400000005, 0.40002020730000004, 0.5540686418, 1.0], 'PGroup_1': [0.8691639814000001, 0.9023603707000001, 0.7737615901, 1.0]}\n",
      "replacing sizeMult\n",
      "{} --> {'PGroup_0': 1.0, 'PGroup_1': 1.0}\n",
      "replacing showParts\n",
      "{} --> {'PGroup_0': True, 'PGroup_1': True}\n",
      "replacing UIparticle\n",
      "{} --> {'PGroup_0': True, 'PGroup_1': True}\n",
      "replacing UIdropdown\n",
      "{} --> {'PGroup_0': True, 'PGroup_1': True}\n",
      "replacing UIcolorPicker\n",
      "{} --> {'PGroup_0': True, 'PGroup_1': True}\n",
      "replacing filterLims\n",
      "{} --> {'PGroup_0': {}, 'PGroup_1': {'field0': None, 'field1': None}}\n",
      "replacing filterVals\n",
      "{} --> {'PGroup_0': {}, 'PGroup_1': {'field0': None, 'field1': None}}\n",
      "replacing colormapLims\n",
      "{} --> {'PGroup_0': {}, 'PGroup_1': {'field0': None, 'field1': None}}\n",
      "replacing colormapVals\n",
      "{} --> {'PGroup_0': {}, 'PGroup_1': {'field0': None, 'field1': None}}\n",
      "replacing colormap\n",
      "{} --> {'PGroup_0': 0.015625, 'PGroup_1': 0.015625}\n",
      "replacing colormapVariable\n",
      "{} --> {'PGroup_0': 0, 'PGroup_1': 0}\n",
      "replacing showColormap\n",
      "{} --> {'PGroup_0': False, 'PGroup_1': False}\n"
     ]
    }
   ],
   "source": [
    "## initialize a new settings object\n",
    "new_settings = Settings()\n",
    "\n",
    "## import the settings from what we just saved above; prints the settings that are updated\n",
    "new_settings.loadFromJSON(\"./mySettings.json\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Attaching a ParticleGroup to a Settings\n",
    "One other thing you may want to do (perhaps in the course of building your own custom `Reader` sub-class) is link a `Firefly.data_reader.ParticleGroup` object to a `Settings` object so that the different particle settings can be imported. \n",
    "`ParticleGroup` settings can be changed in `settings_default` attribute (which is just a normal python dictionary). "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "## create a test particle group\n",
    "particleGroup = ParticleGroup('test',coords)\n",
    "## update the color of this particle group *before* attaching it to a settings object\n",
    "particleGroup.settings_default['color'] = [0,0,1,1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(particleGroup.attached_settings is new_settings) = True\n",
      "\n",
      "--  Settings: particle startup settings --\n",
      "plotNmax {'PGroup_0': None, 'PGroup_1': None, 'test': None}\n",
      "showVel {'PGroup_0': False, 'PGroup_1': False, 'test': False}\n",
      "velType {'PGroup_0': None, 'PGroup_1': None, 'test': None}\n",
      "color {'PGroup_0': [0.047194449400000005, 0.40002020730000004, 0.5540686418, 1.0], 'PGroup_1': [0.8691639814000001, 0.9023603707000001, 0.7737615901, 1.0], 'test': [0, 0, 1, 1]}\n",
      "sizeMult {'PGroup_0': 1.0, 'PGroup_1': 1.0, 'test': 1.0}\n",
      "showParts {'PGroup_0': True, 'PGroup_1': True, 'test': True}\n",
      "\n",
      "--  Settings: particle UI settings --\n",
      "UIparticle {'PGroup_0': True, 'PGroup_1': True, 'test': True}\n",
      "UIdropdown {'PGroup_0': True, 'PGroup_1': True, 'test': True}\n",
      "UIcolorPicker {'PGroup_0': True, 'PGroup_1': True, 'test': True}\n",
      "\n",
      "--  Settings: particle filter settings --\n",
      "filterLims {'PGroup_0': {}, 'PGroup_1': {'field0': None, 'field1': None}, 'test': {}}\n",
      "filterVals {'PGroup_0': {}, 'PGroup_1': {'field0': None, 'field1': None}, 'test': {}}\n",
      "\n",
      "--  Settings: particle colormap settings --\n",
      "colormapLims {'PGroup_0': {}, 'PGroup_1': {'field0': None, 'field1': None}, 'test': {}}\n",
      "colormapVals {'PGroup_0': {}, 'PGroup_1': {'field0': None, 'field1': None}, 'test': {}}\n",
      "colormap {'PGroup_0': 0.015625, 'PGroup_1': 0.015625, 'test': 0.015625}\n",
      "colormapVariable {'PGroup_0': 0, 'PGroup_1': 0, 'test': 0}\n",
      "showColormap {'PGroup_0': False, 'PGroup_1': False, 'test': False}\n",
      "\n"
     ]
    }
   ],
   "source": [
    "## attach the particle group to the settings object\n",
    "##  you can find the settings in the \"particleGroup.attached_settings attribute\"\n",
    "new_settings.attachSettings(particleGroup)\n",
    "print('(particleGroup.attached_settings is new_settings) =',particleGroup.attached_settings is new_settings)\n",
    "print()\n",
    "particleGroup.attached_settings.printKeys(pattern='particle')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Notice that the `'test'` particle group now appears in the particle settings dictionaries (and in particular, note that `settings['color']['test'] = [0,0,1,1]`."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
