diff --git a/package.json b/package.json
index fb08407f448f9bb11b7ade82697b1272147f0cda..04ddb7088000c63d48ff074d7893b1f927500d91 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,7 @@
     "build-min": "webpack --config webpack.prod.js",
     "build": "webpack --config webpack.dev.js",
     "dev-server": "webpack-dev-server --config webpack.dev.js --mode development",
+    "serve-plugins" : "node src/plugin_examples/server.js",
     "test": "echo \"Error: no test specified\" && exit 1"
   },
   "keywords": [],
diff --git a/src/atlasViewer/atlasViewer.apiService.service.ts b/src/atlasViewer/atlasViewer.apiService.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b0d0c59842c00c652c5507bdb1ee901bde00015e
--- /dev/null
+++ b/src/atlasViewer/atlasViewer.apiService.service.ts
@@ -0,0 +1,118 @@
+import { Injectable, Renderer2 } from "@angular/core";
+import { Store, select } from "@ngrx/store";
+import { ViewerStateInterface, safeFilter } from "../services/stateStore.service";
+import { Observable } from "rxjs";
+import { map } from "rxjs/operators";
+
+declare var window
+
+@Injectable({
+  providedIn : 'root'
+})
+
+export class AtlasViewerAPIServices{
+
+  private loadedTemplates$ : Observable<any>
+  public interactiveViewer : InteractiveViewerInterface
+
+  public loadedLibraries : Map<string,{counter:number,src:HTMLElement|null}> = new Map()
+
+  constructor(
+    private store : Store<ViewerStateInterface>
+  ){
+
+    this.loadedTemplates$ = this.store.pipe(
+      select('viewerState'),
+      safeFilter('fetchedTemplates'),
+      map(state=>state.fetchedTemplates)
+    )
+
+    this.interactiveViewer = {
+      metadata : {
+        selectedTemplateBSubject : this.store.pipe(
+          select('viewerState'),
+          safeFilter('templateSelected'),
+          map(state=>state.templateSelected)),
+
+        selectedParcellationBSubject : this.store.pipe(
+          select('viewerState'),
+          safeFilter('parcellationSelected'),
+          map(state=>state.parcellationSelected)),
+
+        selectedRegionsBSubject : this.store.pipe(
+          select('viewerState'),
+          safeFilter('regionsSelected'),
+          map(state=>state.regionsSelected)),
+
+        loadedTemplates : [],
+
+        regionsLabelIndexMap : new Map(),
+
+        datasetsBSubject : this.store.pipe(
+          select('dataStore'),
+          safeFilter('fetchedDataEntries'),
+          map(state=>state.fetchedDataEntries)
+        )
+      },
+      uiHandle : {},
+      pluginControl : {
+        loadExternalLibraries : ()=>Promise.reject('load External Library method not over written')
+        ,
+        unloadExternalLibraries : ()=>{
+          console.warn('unloadExternalLibrary method not overwritten by atlasviewer')
+        }
+      }
+    }
+    window['interactiveViewer'] = this.interactiveViewer
+    this.init()
+  }
+
+  private init(){
+    this.loadedTemplates$.subscribe(templates=>this.interactiveViewer.metadata.loadedTemplates = templates)
+  }
+}
+
+export interface InteractiveViewerInterface{
+
+  metadata : {
+    selectedTemplateBSubject : Observable<any|null>
+    selectedParcellationBSubject : Observable<any|null>
+    selectedRegionsBSubject : Observable<any[]|null>
+    loadedTemplates : any[]
+    regionsLabelIndexMap : Map<number,any> | null
+    datasetsBSubject : Observable<any[]>
+  },
+
+  viewerHandle? : {
+    setNavigationLoc : (coordinates:[number,number,number],realSpace?:boolean)=>void
+    moveToNavigationLoc : (coordinates:[number,number,number],realSpace?:boolean)=>void
+    setNavigationOri : (quat:[number,number,number,number])=>void
+    moveToNavigationOri : (quat:[number,number,number,number])=>void
+    showSegment : (labelIndex : number)=>void
+    hideSegment : (labelIndex : number)=>void
+    showAllSegments : ()=>void
+    hideAllSegments : ()=>void
+    segmentColourMap : Map<number,{red:number,green:number,blue:number}>
+    applyColourMap : (newColourMap : Map<number,{red:number,green:number,blue:number}>)=>void
+    loadLayer : (layerobj:NGLayerObj)=>NGLayerObj
+    removeLayer : (condition:{name : string | RegExp})=>string[]
+    setLayerVisibility : (condition:{name : string|RegExp},visible:boolean)=>void
+
+    mouseEvent : Observable<{eventName:string,event:MouseEvent}>
+    mouseOverNehuba : Observable<{labelIndex : number, foundRegion : any | null}>
+  }
+
+  uiHandle : {
+    
+  }
+
+  pluginControl : {
+    loadExternalLibraries : (libraries:string[])=>Promise<void>
+    unloadExternalLibraries : (libraries:string[])=>void
+    [key:string] : any
+  }
+}
+
+export interface NGLayerObj{
+
+}
\ No newline at end of file
diff --git a/src/atlasViewer/atlasViewer.component.ts b/src/atlasViewer/atlasViewer.component.ts
index ef92e3e159f41a86e8b9bc75fadffe50bac2cca2..f7b94e6fef819da278d6f4084dd592f634b4feaa 100644
--- a/src/atlasViewer/atlasViewer.component.ts
+++ b/src/atlasViewer/atlasViewer.component.ts
@@ -1,133 +1,194 @@
-import { Component, HostBinding, ViewChild, ViewContainerRef, ComponentFactoryResolver, ComponentFactory, OnDestroy, ElementRef, Injector, ComponentRef, AfterViewInit, OnInit, TemplateRef, HostListener } from "@angular/core";
+import { Component, HostBinding, ViewChild, ViewContainerRef, ComponentFactoryResolver, ComponentFactory, OnDestroy, ElementRef, Injector, ComponentRef, AfterViewInit, OnInit, TemplateRef, HostListener, Renderer2 } from "@angular/core";
 import { Store, select } from "@ngrx/store";
-import { ViewerStateInterface, safeFilter, OPEN_SIDE_PANEL, CLOSE_SIDE_PANEL, isDefined, NEWVIEWER, viewerState, CHANGE_NAVIGATION, SELECT_REGIONS, getLabelIndexMap, LOAD_DEDICATED_LAYER, UNLOAD_DEDICATED_LAYER } from "../services/stateStore.service";
-import { Observable, Subscription, combineLatest, merge } from "rxjs";
-import { map, filter, scan, take, distinctUntilChanged } from "rxjs/operators";
+import { ViewerStateInterface, safeFilter, OPEN_SIDE_PANEL, CLOSE_SIDE_PANEL, isDefined,UNLOAD_DEDICATED_LAYER } from "../services/stateStore.service";
+import { Observable, Subscription } from "rxjs";
+import { map, filter, distinctUntilChanged } from "rxjs/operators";
 import { AtlasViewerDataService } from "./atlasViewer.dataService.service";
 import { WidgetServices } from "./widgetUnit/widgetService.service";
 import { DataBrowserUI } from "../ui/databrowser/databrowser.component";
 import { LayoutMainSide } from "../layouts/mainside/mainside.component";
 import { Chart } from 'chart.js'
-import { AtlasViewerConstantsServices } from "./atlasViewer.constantService.service";
+import { AtlasViewerConstantsServices, SUPPORT_LIBRARY_MAP } from "./atlasViewer.constantService.service";
 import { BsModalService } from "ngx-bootstrap/modal";
 import { ModalUnit } from "./modalUnit/modalUnit.component";
 import { AtlasViewerURLService } from "./atlasViewer.urlService.service";
 import { ToastComponent } from "../components/toast/toast.component";
 import { WidgetUnit } from "./widgetUnit/widgetUnit.component";
+import { AtlasViewerAPIServices } from "./atlasViewer.apiService.service";
+import { PluginServices } from "./atlasViewer.pluginService.service";
 
+import '../res/css/extra_styles.css'
 
 @Component({
-  selector : 'atlas-viewer',
-  templateUrl : './atlasViewer.template.html',
-  styleUrls : [
+  selector: 'atlas-viewer',
+  templateUrl: './atlasViewer.template.html',
+  styleUrls: [
     `./atlasViewer.style.css`
   ]
 })
 
-export class AtlasViewer implements OnDestroy,OnInit,AfterViewInit{
+export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
 
-  @ViewChild('dockedContainer',{read:ViewContainerRef}) dockedContainer : ViewContainerRef
-  @ViewChild('floatingContainer',{read:ViewContainerRef}) floatingContainer : ViewContainerRef
-  @ViewChild('databrowser',{read:ElementRef}) databrowser : ElementRef
-  @ViewChild('temporaryContainer',{read:ViewContainerRef}) temporaryContainer : ViewContainerRef
+  @ViewChild('dockedContainer', { read: ViewContainerRef }) dockedContainer: ViewContainerRef
+  @ViewChild('floatingContainer', { read: ViewContainerRef }) floatingContainer: ViewContainerRef
+  @ViewChild('databrowser', { read: ElementRef }) databrowser: ElementRef
+  @ViewChild('temporaryContainer', { read: ViewContainerRef }) temporaryContainer: ViewContainerRef
 
-  @ViewChild('toastContainer',{read:ViewContainerRef}) toastContainer : ViewContainerRef
-  @ViewChild('dedicatedViewerToast',{read:TemplateRef}) dedicatedViewerToast : TemplateRef<any>
+  @ViewChild('toastContainer', { read: ViewContainerRef }) toastContainer: ViewContainerRef
+  @ViewChild('dedicatedViewerToast', { read: TemplateRef }) dedicatedViewerToast: TemplateRef<any>
 
-  @ViewChild('floatingMouseContextualContainer', {read:ViewContainerRef}) floatingMouseContextualContainer : ViewContainerRef
+  @ViewChild('floatingMouseContextualContainer', { read: ViewContainerRef }) floatingMouseContextualContainer: ViewContainerRef
 
-  @ViewChild(LayoutMainSide) layoutMainSide : LayoutMainSide
+  @ViewChild('pluginFactory', { read: ViewContainerRef }) pluginViewContainerRef: ViewContainerRef
 
-  @HostBinding('attr.darktheme') 
-  darktheme : boolean = false
+  @ViewChild(LayoutMainSide) layoutMainSide: LayoutMainSide
 
-  meetsRequirement : boolean = true
+  @HostBinding('attr.darktheme')
+  darktheme: boolean = false
 
-  toastComponentFactory : ComponentFactory<ToastComponent>
-  databrowserComponentFactory : ComponentFactory<DataBrowserUI>
-  databrowserComponentRef : ComponentRef<DataBrowserUI>
-  private databrowserHostComponentRef :ComponentRef<WidgetUnit>
-  private dedicatedViewComponentRef : ComponentRef<ToastComponent>
+  meetsRequirement: boolean = true
 
-  private newViewer$ : Observable<any>
-  public dedicatedView$ : Observable<string|null>
-  public onhoverSegment$ : Observable<string>
-  private subscriptions : Subscription[] = []
+  toastComponentFactory: ComponentFactory<ToastComponent>
+  databrowserComponentFactory: ComponentFactory<DataBrowserUI>
+  databrowserComponentRef: ComponentRef<DataBrowserUI>
+  private databrowserHostComponentRef: ComponentRef<WidgetUnit>
+  private dedicatedViewComponentRef: ComponentRef<ToastComponent>
+
+  private newViewer$: Observable<any>
+  public dedicatedView$: Observable<string | null>
+  public onhoverSegment$: Observable<string>
+  private subscriptions: Subscription[] = []
 
   constructor(
-    private store : Store<ViewerStateInterface>,
-    public dataService : AtlasViewerDataService,
-    private cfr : ComponentFactoryResolver,
-    private widgetServices : WidgetServices,
-    private constantsService : AtlasViewerConstantsServices,
-    public urlService : AtlasViewerURLService,
-    private modalService:BsModalService,
-    private injector : Injector
-  ){
+    private pluginService: PluginServices,
+    private rd2: Renderer2,
+    private store: Store<ViewerStateInterface>,
+    public dataService: AtlasViewerDataService,
+    private cfr: ComponentFactoryResolver,
+    private widgetServices: WidgetServices,
+    private constantsService: AtlasViewerConstantsServices,
+    public urlService: AtlasViewerURLService,
+    public apiService: AtlasViewerAPIServices,
+    private modalService: BsModalService,
+    private injector: Injector
+  ) {
     this.toastComponentFactory = this.cfr.resolveComponentFactory(ToastComponent)
     this.databrowserComponentFactory = this.cfr.resolveComponentFactory(DataBrowserUI)
-    this.databrowserComponentRef = this.databrowserComponentFactory.create( this.injector )
+    this.databrowserComponentRef = this.databrowserComponentFactory.create(this.injector)
 
     this.newViewer$ = this.store.pipe(
       select('viewerState'),
-      filter(state=>isDefined(state) && isDefined(state.templateSelected)),
-      map(state=>state.templateSelected),
-      distinctUntilChanged((t1,t2)=>t1.name === t2.name)
+      filter(state => isDefined(state) && isDefined(state.templateSelected)),
+      map(state => state.templateSelected),
+      distinctUntilChanged((t1, t2) => t1.name === t2.name)
     )
 
     this.dedicatedView$ = this.store.pipe(
       select('viewerState'),
-      filter(state=>isDefined(state)&& typeof state.dedicatedView !== 'undefined'),
-      map(state=>state.dedicatedView),
+      filter(state => isDefined(state) && typeof state.dedicatedView !== 'undefined'),
+      map(state => state.dedicatedView),
       distinctUntilChanged()
     )
-    
+
     this.onhoverSegment$ = this.store.pipe(
       select('uiState'),
-      filter(state=>isDefined(state)),
-      map(state=>state.mouseOverSegment ?
-        state.mouseOverSegment.constructor === Number ? 
-          state.mouseOverSegment.toString() : 
-          state.mouseOverSegment.name :
-        '' ),
+      /* cannot filter by state, as the template expects a default value, or it will throw ExpressionChangedAfterItHasBeenCheckedError */
+      map(state => isDefined(state) ?
+        state.mouseOverSegment ?
+          state.mouseOverSegment.constructor === Number ?
+            state.mouseOverSegment.toString() :
+            state.mouseOverSegment.name :
+          '' :
+        ''),
       distinctUntilChanged()
     )
-
   }
 
-  ngOnInit(){
+  ngOnInit() {
+
+    this.apiService.interactiveViewer.pluginControl.loadExternalLibraries = (libraries: string[]) => new Promise((resolve, reject) => {
+      const srcHTMLElement = libraries.map(libraryName => ({
+        name: libraryName,
+        srcEl: SUPPORT_LIBRARY_MAP.get(libraryName)
+      }))
+
+      const rejected = srcHTMLElement.filter(scriptObj => scriptObj.srcEl === null)
+      if (rejected.length > 0)
+        return reject(`Some library names cannot be recognised. No libraries were loaded: ${rejected.map(srcObj => srcObj.name).join(', ')}`)
+
+      Promise.all(srcHTMLElement.map(scriptObj => new Promise((rs, rj) => {
+        if('customElements' in window && scriptObj.name === 'webcomponentsLite'){
+          return rs()
+        }
+        const existingEntry = this.apiService.loadedLibraries.get(scriptObj.name)
+        if (existingEntry) {
+          this.apiService.loadedLibraries.set(scriptObj.name, { counter: existingEntry.counter + 1, src: existingEntry.src })
+          rs()
+        } else {
+          const srcEl = scriptObj.srcEl
+          srcEl.onload = () => rs()
+          srcEl.onerror = (e: any) => rj(e)
+          this.rd2.appendChild(document.head, srcEl)
+          this.apiService.loadedLibraries.set(scriptObj.name, { counter: 1, src: srcEl })
+        }
+      })))
+        .then(() => resolve())
+        .catch(e => (console.warn(e), reject(e)))
+    })
+
+    this.apiService.interactiveViewer.pluginControl.unloadExternalLibraries = (libraries: string[]) =>
+      libraries
+        .filter((stringname) => SUPPORT_LIBRARY_MAP.get(stringname) !== null)
+        .forEach(libname => {
+          const ledger = this.apiService.loadedLibraries.get(libname!)
+          if (!ledger) {
+            console.warn('unload external libraries error. cannot find ledger entry...', libname, this.apiService.loadedLibraries)
+            return
+          }
+          if (ledger.src === null) {
+            console.log('webcomponents is native supported. no library needs to be unloaded')
+            return
+          }
+
+          if (ledger.counter - 1 == 0) {
+            this.rd2.removeChild(document.head, ledger.src)
+            this.apiService.loadedLibraries.delete(libname!)
+          } else {
+            this.apiService.loadedLibraries.set(libname!, { counter: ledger.counter - 1, src: ledger.src })
+          }
+        })
 
     this.meetsRequirement = this.meetsRequirements()
 
     this.subscriptions.push(
-      this.dedicatedView$.subscribe(string=>{
-        if(string === null){
-          if(this.dedicatedViewComponentRef)
+      this.dedicatedView$.subscribe(string => {
+        if (string === null) {
+          if (this.dedicatedViewComponentRef)
             this.dedicatedViewComponentRef.destroy()
           return
         }
-        this.dedicatedViewComponentRef = this.toastContainer.createComponent( this.toastComponentFactory )
-        this.dedicatedViewComponentRef.instance.messageContainer.createEmbeddedView( this.dedicatedViewerToast )
+        this.dedicatedViewComponentRef = this.toastContainer.createComponent(this.toastComponentFactory)
+        this.dedicatedViewComponentRef.instance.messageContainer.createEmbeddedView(this.dedicatedViewerToast)
         this.dedicatedViewComponentRef.instance.dismissable = false
       })
     )
 
     this.subscriptions.push(
-      this.newViewer$.subscribe(template=>{
-        this.darktheme = this.meetsRequirement ? 
+      this.newViewer$.subscribe(template => {
+        this.darktheme = this.meetsRequirement ?
           template.useTheme === 'dark' :
           false
 
-        if(this.databrowserHostComponentRef){
+        if (this.databrowserHostComponentRef) {
           this.databrowserHostComponentRef.instance.container.detach(0)
-          this.temporaryContainer.insert( this.databrowserComponentRef.hostView )
+          this.temporaryContainer.insert(this.databrowserComponentRef.hostView)
         }
         this.widgetServices.clearAllWidgets()
-        this.databrowserHostComponentRef = 
-          this.widgetServices.addNewWidget(this.databrowserComponentRef,{
-            title : 'Data Browser',
-            exitable :false,
-            state : 'docked'
+        this.databrowserHostComponentRef =
+          this.widgetServices.addNewWidget(this.databrowserComponentRef, {
+            title: 'Data Browser',
+            exitable: false,
+            state: 'docked'
           })
       })
     )
@@ -136,9 +197,9 @@ export class AtlasViewer implements OnDestroy,OnInit,AfterViewInit{
       this.store.pipe(
         select('uiState'),
         safeFilter('sidePanelOpen'),
-        map(state=>state.sidePanelOpen)
-      ).subscribe(show=>
-        this.layoutMainSide.showSide=show)
+        map(state => state.sidePanelOpen)
+      ).subscribe(show =>
+        this.layoutMainSide.showSide = show)
     )
 
     /**
@@ -147,55 +208,55 @@ export class AtlasViewer implements OnDestroy,OnInit,AfterViewInit{
     Chart.pluginService.register({
 
       /* patching background color fill, so saved images do not look completely white */
-      beforeDraw: (chart)=>{
+      beforeDraw: (chart) => {
         const ctx = chart.ctx as CanvasRenderingContext2D;
-        ctx.fillStyle = this.darktheme ? 
+        ctx.fillStyle = this.darktheme ?
           `rgba(50,50,50,0.8)` :
           `rgba(255,255,255,0.8)`
-          
-        if(chart.canvas)ctx.fillRect(0,0,chart.canvas.width,chart.canvas.height)
-        
+
+        if (chart.canvas) ctx.fillRect(0, 0, chart.canvas.width, chart.canvas.height)
+
       },
 
       /* patching standard deviation for polar (potentially also line/bar etc) graph */
-      afterInit : (chart)=>{
-        if(chart.config.options && chart.config.options.tooltips){
-          
+      afterInit: (chart) => {
+        if (chart.config.options && chart.config.options.tooltips) {
+
           chart.config.options.tooltips.callbacks = {
-            label : function(tooltipItem,data){
+            label: function (tooltipItem, data) {
               let sdValue
-              if( data.datasets && typeof tooltipItem.datasetIndex != 'undefined' && data.datasets[tooltipItem.datasetIndex].label ){
-                const sdLabel = data.datasets[tooltipItem.datasetIndex].label+'_sd'
-                const sd = data.datasets.find(dataset=> typeof dataset.label != 'undefined' && dataset.label == sdLabel)
-                if(sd && sd.data && typeof tooltipItem.index != 'undefined' && typeof tooltipItem.yLabel != 'undefined') sdValue = Number(sd.data[tooltipItem.index]) - Number(tooltipItem.yLabel)
+              if (data.datasets && typeof tooltipItem.datasetIndex != 'undefined' && data.datasets[tooltipItem.datasetIndex].label) {
+                const sdLabel = data.datasets[tooltipItem.datasetIndex].label + '_sd'
+                const sd = data.datasets.find(dataset => typeof dataset.label != 'undefined' && dataset.label == sdLabel)
+                if (sd && sd.data && typeof tooltipItem.index != 'undefined' && typeof tooltipItem.yLabel != 'undefined') sdValue = Number(sd.data[tooltipItem.index]) - Number(tooltipItem.yLabel)
               }
-              return `${tooltipItem.yLabel} ${sdValue ? '('+ sdValue +')' : ''}`
+              return `${tooltipItem.yLabel} ${sdValue ? '(' + sdValue + ')' : ''}`
             }
           }
         }
-        if(chart.data.datasets){
+        if (chart.data.datasets) {
           chart.data.datasets = chart.data.datasets
-            .map(dataset=>{
-              if(dataset.label && /\_sd$/.test(dataset.label)){
-                const originalDS = chart.data.datasets!.find(baseDS=>typeof baseDS.label!== 'undefined' && (baseDS.label == dataset.label!.replace(/_sd$/,'')))
-                if(originalDS){
-                  return Object.assign({},dataset,{
-                    data : (originalDS.data as number[]).map((datapoint,idx)=>(Number(datapoint) + Number((dataset.data as number[])[idx]))),
+            .map(dataset => {
+              if (dataset.label && /\_sd$/.test(dataset.label)) {
+                const originalDS = chart.data.datasets!.find(baseDS => typeof baseDS.label !== 'undefined' && (baseDS.label == dataset.label!.replace(/_sd$/, '')))
+                if (originalDS) {
+                  return Object.assign({}, dataset, {
+                    data: (originalDS.data as number[]).map((datapoint, idx) => (Number(datapoint) + Number((dataset.data as number[])[idx]))),
                     ... this.constantsService.chartSdStyle
                   })
-                }else{
+                } else {
                   return dataset
                 }
-              }else if (dataset.label){
-                const sdDS = chart.data.datasets!.find(sdDS=>typeof sdDS.label !=='undefined' && (sdDS.label == dataset.label + '_sd'))
-                if(sdDS){
-                  return Object.assign({},dataset,{
+              } else if (dataset.label) {
+                const sdDS = chart.data.datasets!.find(sdDS => typeof sdDS.label !== 'undefined' && (sdDS.label == dataset.label + '_sd'))
+                if (sdDS) {
+                  return Object.assign({}, dataset, {
                     ...this.constantsService.chartBaseStyle
                   })
-                }else{
+                } else {
                   return dataset
                 }
-              }else{
+              } else {
                 return dataset
               }
             })
@@ -204,39 +265,42 @@ export class AtlasViewer implements OnDestroy,OnInit,AfterViewInit{
     })
   }
 
-  ngOnDestroy(){
-    this.subscriptions.forEach(s=>s.unsubscribe())
+  ngOnDestroy() {
+    this.subscriptions.forEach(s => s.unsubscribe())
   }
 
-  ngAfterViewInit(){
+  ngAfterViewInit() {
     this.widgetServices.floatingContainer = this.floatingContainer
     this.widgetServices.dockedContainer = this.dockedContainer
+
+    this.pluginService.pluginViewContainerRef = this.pluginViewContainerRef
+    this.pluginService.appendSrc = (src: HTMLElement) => this.rd2.appendChild(document.head, src)
   }
 
-  meetsRequirements(){
-    
+  meetsRequirements() {
+
     const canvas = document.createElement('canvas')
     const gl = canvas.getContext('webgl')
-    const message:any = {
-      Error:'Your browser does not meet the minimum requirements to run neuroglancer.'
+    const message: any = {
+      Error: 'Your browser does not meet the minimum requirements to run neuroglancer.'
     }
 
-    if(!gl){
+    if (!gl) {
       message['Detail'] = 'Your browser does not support WebGL.'
-      
-      this.modalService.show(ModalUnit,{
-        initialState : {
-          title : message.Error,
-          body : message.Detail
+
+      this.modalService.show(ModalUnit, {
+        initialState: {
+          title: message.Error,
+          body: message.Detail
         }
       })
       return false
     }
-    
+
     const drawbuffer = gl.getExtension('WEBGL_draw_buffers')
     const texturefloat = gl.getExtension('OES_texture_float')
     const indexuint = gl.getExtension('OES_element_index_uint')
-    if( !(drawbuffer && texturefloat && indexuint) ){
+    if (!(drawbuffer && texturefloat && indexuint)) {
 
       const detail = `Your browser does not support 
       ${ !drawbuffer ? 'WEBGL_draw_buffers' : ''} 
@@ -244,10 +308,10 @@ export class AtlasViewer implements OnDestroy,OnInit,AfterViewInit{
       ${ !indexuint ? 'OES_element_index_uint' : ''} `
       message['Detail'] = [detail]
 
-      this.modalService.show(ModalUnit,{
-        initialState : {
-          title : message.Error,
-          body : message.Detail
+      this.modalService.show(ModalUnit, {
+        initialState: {
+          title: message.Error,
+          body: message.Detail
         }
       })
       return false
@@ -255,26 +319,26 @@ export class AtlasViewer implements OnDestroy,OnInit,AfterViewInit{
     return true
   }
 
-  manualPanelToggle(show:boolean){
+  manualPanelToggle(show: boolean) {
     this.store.dispatch({
-      type : show ? OPEN_SIDE_PANEL : CLOSE_SIDE_PANEL
+      type: show ? OPEN_SIDE_PANEL : CLOSE_SIDE_PANEL
     })
   }
 
-  clearDedicatedView(){
+  clearDedicatedView() {
     this.store.dispatch({
-      type : UNLOAD_DEDICATED_LAYER
+      type: UNLOAD_DEDICATED_LAYER
     })
   }
 
-  mousePos : [number,number] = [0,0]
+  mousePos: [number, number] = [0, 0]
 
-  @HostListener('mousemove',['$event'])
-  mousemove(event:MouseEvent){
-    this.mousePos = [event.clientX,event.clientY]
+  @HostListener('mousemove', ['$event'])
+  mousemove(event: MouseEvent) {
+    this.mousePos = [event.clientX, event.clientY]
   }
 
-  get floatingMouseContextualContainerTransform(){
+  get floatingMouseContextualContainerTransform() {
     return `translate(${this.mousePos[0]}px,${this.mousePos[1]}px)`
   }
 }
\ No newline at end of file
diff --git a/src/atlasViewer/atlasViewer.dataService.service.ts b/src/atlasViewer/atlasViewer.dataService.service.ts
index 524eb69f2f16c8a820ba743ad7fc161ec7dc8720..a9f0423179d256375ba1bbbaf92cfb6e65267804 100644
--- a/src/atlasViewer/atlasViewer.dataService.service.ts
+++ b/src/atlasViewer/atlasViewer.dataService.service.ts
@@ -4,6 +4,7 @@ import { ViewerStateInterface, FETCHED_TEMPLATES, DataEntry, FETCHED_DATAENTRIES
 import { map, distinctUntilChanged } from "rxjs/operators";
 import { Subscription } from "rxjs";
 import { AtlasViewerConstantsServices } from "./atlasViewer.constantService.service";
+import { PluginManifest } from "./atlasViewer.pluginService.service";
 
 @Injectable({
   providedIn : 'root'
@@ -11,22 +12,38 @@ import { AtlasViewerConstantsServices } from "./atlasViewer.constantService.serv
 export class AtlasViewerDataService implements OnDestroy{
   
   private subscriptions : Subscription[] = []
+
+  public promiseFetchedPluginManifests : Promise<PluginManifest[]> = new Promise((resolve,reject)=>{
+    // fetch('http://medpc055.ime.kfa-juelich.de:5080/collectPlugins')
+    //   .then(res=>res.json())
+    //   .then(json=>resolve(json))
+    //   .catch(err=>reject(err))
+    
+    Promise.all([
+      fetch('http://localhost:10080/jugex/manifest.json').then(res=>res.json()),
+      fetch('http://localhost:10080/testPlugin/manifest.json').then(res=>res.json())
+    ])
+      .then(arr=>resolve(arr))
+      .catch(e=>reject(e))
+  })
+
+  public promiseFetchedTemplates : Promise<any[]> = Promise.all(this.constantService.templateUrls.map(url=>
+    fetch(url)
+      .then(res=>
+        res.json())
+      .then(json=>json.nehubaConfig && !json.nehubaConfigURL ? 
+        Promise.resolve(json) :
+        fetch(json.nehubaConfigURL)
+          .then(r=>r.json())
+          .then(nehubaConfig=>Promise.resolve(Object.assign({},json,{ nehubaConfig })))
+      )))
   
   constructor(
     private store : Store<ViewerStateInterface>,
     private constantService : AtlasViewerConstantsServices
   ){
 
-    Promise.all(this.constantService.templateUrls.map(url=>
-      fetch(url)
-        .then(res=>
-          res.json())
-        .then(json=>json.nehubaConfig && !json.nehubaConfigURL ? 
-          Promise.resolve(json) :
-          fetch(json.nehubaConfigURL)
-            .then(r=>r.json())
-            .then(nehubaConfig=>Promise.resolve(Object.assign({},json,{ nehubaConfig })))
-        )))
+    this.promiseFetchedTemplates
       .then(arrJson=>
         this.store.dispatch({
           type : FETCHED_TEMPLATES,
diff --git a/src/atlasViewer/atlasViewer.pluginService.service.ts b/src/atlasViewer/atlasViewer.pluginService.service.ts
new file mode 100644
index 0000000000000000000000000000000000000000..81f6f926d7f946e46f63b28b0ad71aefa01290c9
--- /dev/null
+++ b/src/atlasViewer/atlasViewer.pluginService.service.ts
@@ -0,0 +1,153 @@
+import { Injectable, ViewContainerRef, ComponentFactoryResolver, ComponentFactory } from "@angular/core";
+import { AtlasViewerDataService } from "./atlasViewer.dataService.service";
+import { isDefined } from "../services/stateStore.service";
+import { AtlasViewerAPIServices } from "./atlasViewer.apiService.service";
+import { PluginUnit } from "./pluginUnit/pluginUnit.component";
+import { WidgetServices } from "./widgetUnit/widgetService.service";
+
+import '../res/css/plugin_styles.css'
+import { interval } from "rxjs";
+import { take, takeUntil } from "rxjs/operators";
+
+@Injectable({
+  providedIn : 'root'
+})
+
+export class PluginServices{
+
+  public fetchedPluginManifests : PluginManifest[] = []
+  public pluginViewContainerRef : ViewContainerRef 
+  public appendSrc : (script:HTMLElement)=>void
+  private pluginUnitFactory : ComponentFactory<PluginUnit>
+
+  constructor(
+    private apiService : AtlasViewerAPIServices,
+    private atlasDataService : AtlasViewerDataService,
+    private widgetService : WidgetServices,
+    private cfr : ComponentFactoryResolver
+  ){
+
+    this.pluginUnitFactory = this.cfr.resolveComponentFactory( PluginUnit )
+
+    this.atlasDataService.promiseFetchedPluginManifests
+      .then(arr=>
+        this.fetchedPluginManifests = arr)
+      .catch(console.error)
+  }
+
+  readyPlugin(plugin:PluginManifest):Promise<any>{
+    return Promise.all([
+        isDefined(plugin.template) ?
+          Promise.resolve('template already provided') :
+          isDefined(plugin.templateURL) ?
+            fetch(plugin.templateURL)
+              .then(res=>res.text())
+              .then(template=>plugin.template = template) :
+            Promise.reject('both template and templateURL are not defined') ,
+        isDefined(plugin.script) ?
+          Promise.resolve('script already provided') :
+          isDefined(plugin.scriptURL) ?
+            fetch(plugin.scriptURL)
+              .then(res=>res.text())
+              .then(script=>plugin.script = script) :
+            Promise.reject('both template and templateURL are not defined') 
+      ])
+  }
+
+  launchPlugin(plugin:PluginManifest){
+    if(this.apiService.interactiveViewer.pluginControl[plugin.name])
+    {
+      console.warn('plugin already launched. blinking for 10s.')
+      this.apiService.interactiveViewer.pluginControl[plugin.name].blink(10)
+      return
+    }
+    this.readyPlugin(plugin)
+      .then(()=>{
+        const pluginUnit = this.pluginViewContainerRef.createComponent( this.pluginUnitFactory )
+        /* TODO in v0.2, I used:
+        
+        const template = document.createElement('div')
+        template.insertAdjacentHTML('afterbegin',template)
+
+        // reason was:
+        // changed from innerHTML to insertadjacenthtml to accomodate angular elements ... not too sure about the actual ramification
+
+        */
+        const script = document.createElement('script')
+        script.innerHTML = plugin.script
+        this.appendSrc(script)
+
+        const template = document.createElement('div')
+        template.insertAdjacentHTML('afterbegin',plugin.template)
+        pluginUnit.instance.elementRef.nativeElement.append( template )
+
+        const widgetCompRef = this.widgetService.addNewWidget(pluginUnit,{
+          state : 'floating',
+          exitable : true,
+          title : plugin.name
+        })
+
+        const handler = new PluginHandler()
+        this.apiService.interactiveViewer.pluginControl[plugin.name] = handler
+
+        const unsubscribeOnPluginDestroy = []
+        const shutdownCB = []
+
+        handler.onShutdown = (cb)=>{
+          if(typeof cb !== 'function'){
+            console.warn('onShutdown requires the argument to be a function') 
+            return
+          }
+          shutdownCB.push(cb)
+        }
+
+        handler.blink = (sec?:number)=>{
+          if(typeof sec !== 'number')
+            console.warn(`sec is not a number, default blink interval used`)
+          widgetCompRef.instance.containerClass = ''
+          interval(typeof sec === 'number' ? sec * 1000 : 500).pipe(
+            take(11),
+            takeUntil(widgetCompRef.instance.clickedEmitter)
+          ).subscribe(()=>
+            widgetCompRef.instance.containerClass = widgetCompRef.instance.containerClass === 'panel-success' ? 
+              '' : 
+              'panel-success')
+        }
+
+        unsubscribeOnPluginDestroy.push(
+          widgetCompRef.instance.clickedEmitter.subscribe(()=>
+            widgetCompRef.instance.containerClass = '')
+          )
+
+        handler.shutdown = ()=>{
+          widgetCompRef.instance.exit()
+        }
+
+        handler.onShutdown(()=>{
+          unsubscribeOnPluginDestroy.forEach(s=>s.unsubscribe())
+          delete this.apiService.interactiveViewer.pluginControl[plugin.name]
+        })
+        
+        pluginUnit.onDestroy(()=>{
+          while(shutdownCB.length > 0){
+            shutdownCB.pop()()
+          }
+        })
+      })
+      .catch(console.error)
+  }
+}
+
+export class PluginHandler{
+  onShutdown : (callback:()=>void)=>void
+  blink : (sec?:number)=>void
+  shutdown : ()=>void
+}
+
+export interface PluginManifest{
+  name? : string
+  templateURL? : string
+  template? : string
+  scriptURL? : string
+  script? : string 
+}
\ No newline at end of file
diff --git a/src/atlasViewer/atlasViewer.template.html b/src/atlasViewer/atlasViewer.template.html
index 877b85986a57e0489f0a00d3ae64daf665545f71..6c1d721e5e401df10ee528e1ebe76c6e100f79fe 100644
--- a/src/atlasViewer/atlasViewer.template.html
+++ b/src/atlasViewer/atlasViewer.template.html
@@ -24,13 +24,10 @@
 
     </ng-template>
     <div [style.transform] = "floatingMouseContextualContainerTransform" floatingMouseContextualContainer>
-      <div contextualInnerContainer>
-        <div *ngIf = "(onhoverSegment$ | async) !== ''" contextualBlock>
-          {{ onhoverSegment$ | async }} 
+      <div *ngIf = "onhoverSegment$ | async as onhoverSegment" contextualInnerContainer>
+        <div *ngIf = "onhoverSegment !== ''" contextualBlock>
+          {{ onhoverSegment }} 
         </div>
-        <ng-template #floatingContextContainer>
-  
-        </ng-template>
       </div>
     </div>
     <div toastContainer>
@@ -40,6 +37,9 @@
   </layout-floating-container>
 
   <ng-template #temporaryContainer>
+  </ng-template>
+
+  <ng-template #pluginFactory>
     
   </ng-template>
 
@@ -57,7 +57,3 @@
     no special data is being displayed right now
   </div>
 </ng-template>
-
-<ng-template #mouseoverSegment>
-  {{ onhoverSegment$ | async }}
-</ng-template>
\ No newline at end of file
diff --git a/src/atlasViewer/pluginUnit/pluginUnit.component.ts b/src/atlasViewer/pluginUnit/pluginUnit.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..15e4624040df7c1c6545ba9c9d32b96d82bf1f23
--- /dev/null
+++ b/src/atlasViewer/pluginUnit/pluginUnit.component.ts
@@ -0,0 +1,17 @@
+import { Component, ElementRef, ViewChild, OnDestroy } from "@angular/core";
+
+
+@Component({
+  templateUrl : `./pluginUnit.template.html`
+})
+
+export class PluginUnit implements OnDestroy{
+  
+  @ViewChild('pluginContainer',{read:ElementRef}) 
+  elementRef:ElementRef
+
+  ngOnDestroy(){
+    console.log('plugin being destroyed')
+  }
+
+}
\ No newline at end of file
diff --git a/src/atlasViewer/pluginUnit/pluginUnit.template.html b/src/atlasViewer/pluginUnit/pluginUnit.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..4896a25f9103b94552db10dd920b9f200a1225ec
--- /dev/null
+++ b/src/atlasViewer/pluginUnit/pluginUnit.template.html
@@ -0,0 +1,2 @@
+<div pluginContainer #pluginContainer>
+</div>
\ No newline at end of file
diff --git a/src/atlasViewer/widgetUnit/widgetService.service.ts b/src/atlasViewer/widgetUnit/widgetService.service.ts
index f3e4bdfe7720436ca3a328d4f6ba953312a82d2f..2c45a005887c49a65c099c41a374858557396740 100644
--- a/src/atlasViewer/widgetUnit/widgetService.service.ts
+++ b/src/atlasViewer/widgetUnit/widgetService.service.ts
@@ -33,7 +33,7 @@ export class WidgetServices{
     this.clickedListener.forEach(s=>s.unsubscribe())
   }
 
-  addNewWidget(guestComponentRef:ComponentRef<any>,options?:any){
+  addNewWidget(guestComponentRef:ComponentRef<any>,options?:any):ComponentRef<WidgetUnit>{
     const _option = getOption(options)
     const component = _option.state === 'floating' ? 
       this.floatingContainer.createComponent(this.widgetUnitFactory) :
diff --git a/src/atlasViewer/widgetUnit/widgetUnit.component.ts b/src/atlasViewer/widgetUnit/widgetUnit.component.ts
index 5c15dd4a3301661565a17872ed20d54efd389afe..e9500f6ebca59be5a17d130d0b6434d397d3be84 100644
--- a/src/atlasViewer/widgetUnit/widgetUnit.component.ts
+++ b/src/atlasViewer/widgetUnit/widgetUnit.component.ts
@@ -28,6 +28,8 @@ export class WidgetUnit {
 
   @Input() title : string = 'Untitled'
 
+  @Input() containerClass : string = ''
+
   @Output()
   clickedEmitter : EventEmitter<WidgetUnit> = new EventEmitter()
 
@@ -37,10 +39,12 @@ export class WidgetUnit {
   public cf : ComponentRef<WidgetUnit>
   public widgetServices:WidgetServices
 
-  undock(event:Event){
-    event.stopPropagation()
-    event.preventDefault()
-
+  undock(event?:Event){
+    if(event){
+      event.stopPropagation()
+      event.preventDefault()
+    }
+    
     this.widgetServices.changeState(this,{
       title : this.title,
       state:'floating',
@@ -48,10 +52,12 @@ export class WidgetUnit {
     })
   }
 
-  dock(event:Event){
-    event.stopPropagation()
-    event.preventDefault()
-
+  dock(event?:Event){
+    if(event){
+      event.stopPropagation()
+      event.preventDefault()
+    }
+    
     this.widgetServices.changeState(this,{
       title : this.title,
       state:'docked',
@@ -59,9 +65,11 @@ export class WidgetUnit {
     })
   }
 
-  exit(event:Event){
-    event.stopPropagation()
-    event.preventDefault()
+  exit(event?:Event){
+    if(event){
+      event.stopPropagation()
+      event.preventDefault()
+    }
 
     this.widgetServices.exitWidget(this)
   }
diff --git a/src/atlasViewer/widgetUnit/widgetUnit.template.html b/src/atlasViewer/widgetUnit/widgetUnit.template.html
index c9a35c58479075d83493109a59804cc7d89c4c4f..eb5222c030e44d3528b0ad152f1bfe1a5a13b6cf 100644
--- a/src/atlasViewer/widgetUnit/widgetUnit.template.html
+++ b/src/atlasViewer/widgetUnit/widgetUnit.template.html
@@ -1,5 +1,6 @@
 <panel
   [style.transform] = "transform"
+  [containerClass] = "containerClass"
   widgetUnitPanel
   [bodyCollapsable] = "state === 'docked'"
   >
diff --git a/src/components/components.module.ts b/src/components/components.module.ts
index 6eaf6d29caffc918c15d7c4e2e2f40eebe4f2241..9f8c480e387b7fc582f0244ba700db77e897e8cc 100644
--- a/src/components/components.module.ts
+++ b/src/components/components.module.ts
@@ -16,6 +16,7 @@ import { PanelComponent } from './panel/panel.component';
 import { PaginationComponent } from './pagination/pagination.component';
 import { SearchResultPaginationPipe } from '../util/pipes/pagination.pipe';
 import { ToastComponent } from './toast/toast.component';
+import { TreeSearchPipe } from '../util/pipes/treeSearch.pipe';
 
 @NgModule({
   imports : [
@@ -39,7 +40,8 @@ import { ToastComponent } from './toast/toast.component';
 
     /* pipes */
     SafeHtmlPipe,
-    SearchResultPaginationPipe
+    SearchResultPaginationPipe,
+    TreeSearchPipe
   ],
   exports : [
     MarkdownDom,
@@ -51,6 +53,7 @@ import { ToastComponent } from './toast/toast.component';
     ToastComponent,
 
     SearchResultPaginationPipe,
+    TreeSearchPipe,
 
     HoverableBlockDirective,
 
diff --git a/src/components/panel/panel.component.ts b/src/components/panel/panel.component.ts
index ed81129782de8882d58f58b17a2d4bd90731bf88..5f7220081300be309bf1089413d8de2d102cdbef 100644
--- a/src/components/panel/panel.component.ts
+++ b/src/components/panel/panel.component.ts
@@ -17,6 +17,8 @@ export class PanelComponent{
   @Input() collapseBody : boolean = false
   @Input() bodyCollapsable : boolean = false
 
+  @Input() containerClass : string = ''
+
   toggleCollapseBody(event:Event){
     if(this.bodyCollapsable){
       this.collapseBody = !this.collapseBody
diff --git a/src/components/panel/panel.style.css b/src/components/panel/panel.style.css
index 6a50ea914eb18c8510bce1d27eae5e5d96d93713..1802a29a767f050073e17900faec0ef047053c50 100644
--- a/src/components/panel/panel.style.css
+++ b/src/components/panel/panel.style.css
@@ -2,13 +2,13 @@
 {
   font-size:92%;
   overflow:hidden;
+  box-shadow: 0px 4px 16px -4px rgba(0,0,0,0.2);
 }
 
 .panel
 {
   border-radius : 0;
   border:none;
-  box-shadow: 0px 4px 16px -4px rgba(0,0,0,0.2);
   margin:0;
 }
 
@@ -36,14 +36,25 @@ div.panel
   background:none;
 }
 
-:host-context([darktheme="true"]) div.panel-heading
+div.panel-body
+{
+  background-color:rgba(245,245,245,0.8);
+}
+
+:host-context([darktheme="true"]) div.panel-default div.panel-heading
 {
   background-color:rgba(45,45,45,0.9);
-  color:rgba(255,255,255,0.9);
+  color:rgba(250,250,250,0.9);
+}
+
+:host-context([darktheme="true"]) div.panel-success div.panel-heading
+{
+  background-color:rgba(60,118,61,0.9);
+  color:rgba(223,240,216  ,0.9);
 }
 
 :host-context([darktheme="true"]) div.panel-body
 {
   color:rgba(255,255,255,0.9);
-  background-color:rgba(45,45,45,0.65);
+  background-color:rgba(45,45,45,0.8);
 }
\ No newline at end of file
diff --git a/src/components/panel/panel.template.html b/src/components/panel/panel.template.html
index 853c6ee7031daada8ced86f5586b27432f327eeb..b571a62aefa107f97e60689c35e694208fccaa73 100644
--- a/src/components/panel/panel.template.html
+++ b/src/components/panel/panel.template.html
@@ -1,4 +1,5 @@
-<div class = "panel panel-default">
+<div class = "panel" 
+  [ngClass] = "containerClass === '' ?  'panel-default' : containerClass">
   <div
     *ngIf = "showHeading"
     class = "panel-heading"
diff --git a/src/components/tree/tree.component.ts b/src/components/tree/tree.component.ts
index 081376b31581ed1bafb148bb94cdd6ece39fe056..22d7698dd3d60778989f3d78d9b2c5c4334bf1f8 100644
--- a/src/components/tree/tree.component.ts
+++ b/src/components/tree/tree.component.ts
@@ -1,4 +1,4 @@
-import { Component, Input, Output, EventEmitter, ViewChildren, QueryList, HostBinding } from "@angular/core";
+import { Component, Input, Output, EventEmitter, ViewChildren, QueryList, HostBinding, ChangeDetectionStrategy } from "@angular/core";
 
 
 @Component({
@@ -6,7 +6,8 @@ import { Component, Input, Output, EventEmitter, ViewChildren, QueryList, HostBi
   templateUrl : './tree.template.html',
   styleUrls : [
     './tree.style.css'
-  ]
+  ],
+  changeDetection:ChangeDetectionStrategy.OnPush
 })
 
 export class TreeComponent{
@@ -18,7 +19,7 @@ export class TreeComponent{
   @Input() findChildren : (item:any)=>any[] = (item)=>item.children
   @Input() childrenExpanded : boolean = true
 
-  @Input() searchFilter : (item:any)=>boolean | null = null
+  @Input() searchFilter : (item:any)=>boolean | null = ()=>true
 
   @Output() mouseentertree : EventEmitter<any> = new EventEmitter()
   @Output() mouseleavetree : EventEmitter<any> = new EventEmitter()
@@ -53,8 +54,8 @@ export class TreeComponent{
 
   @HostBinding('attr.filterHidden')
   get visibilityOnFilter():boolean{
-    return (this.searchFilter ? 
-      this.searchFilter(this.inputItem) || (this.treeChildren.some(tree=>tree.visibilityOnFilter) ) :
-      true)
+    return this.searchFilter ? 
+      this.searchFilter(this.inputItem) :
+      false
   }
 }
\ No newline at end of file
diff --git a/src/components/tree/tree.style.css b/src/components/tree/tree.style.css
index fec01230a858b1d61f88a16715c99a96375979a1..22cd7ce11ed5845e590c01a0a4f3f5da278c4b53 100644
--- a/src/components/tree/tree.style.css
+++ b/src/components/tree/tree.style.css
@@ -1,4 +1,4 @@
-tree:not([filterHidden="false"]):not([hidden])
+tree
 {
   display:block;
   margin-left:1em;
@@ -31,7 +31,7 @@ div[itemContainer] > span[itemName]:hover
 
 /* dashed guiding line */
 
-tree:not([filterHidden="false"]):not(:last-child) > div[itemMasterContainer]:before
+tree:not(:last-child) > div[itemMasterContainer]:before
 {
   
   pointer-events: none;
@@ -43,13 +43,13 @@ tree:not([filterHidden="false"]):not(:last-child) > div[itemMasterContainer]:bef
   border-left: rgba(255,255,255,1) 1px dashed;
 }
 
-tree:not([filterHidden="false"]):not(:last-child) div[itemMasterContainer] > [itemContainer]
+tree:not(:last-child) div[itemMasterContainer] > [itemContainer]
 {
   position:relative;
 }
 
 
-tree:not([filterHidden="false"]):not(:last-child) div[itemMasterContainer] > [itemContainer]:before
+tree:not(:last-child) div[itemMasterContainer] > [itemContainer]:before
 {
   pointer-events: none;
   content : '';
@@ -61,12 +61,12 @@ tree:not([filterHidden="false"]):not(:last-child) div[itemMasterContainer] > [it
   z-index: 0;
 }
 
-tree:not([filterHidden="false"]):last-child div[itemMasterContainer] > [itemContainer]
+tree:last-child div[itemMasterContainer] > [itemContainer]
 {
   position:relative;
 }
 
-tree:not([filterHidden="false"]):last-child div[itemMasterContainer] > [itemContainer]:before
+tree:last-child div[itemMasterContainer] > [itemContainer]:before
 {
   pointer-events: none;
   content : '';
@@ -78,17 +78,17 @@ tree:not([filterHidden="false"]):last-child div[itemMasterContainer] > [itemCont
   z-index: 0;
 }
 
-tree:not([filterHidden="false"]):not(:last-child) div[itemMasterContainer]:before
+tree:not(:last-child) div[itemMasterContainer]:before
 {
   border-left: rgba(128,128,128,0.6) 1px dashed;
 }
 
-tree:not([filterHidden="false"]):not(:last-child) div[itemMasterContainer] > [itemContainer]:before
+tree:not(:last-child) div[itemMasterContainer] > [itemContainer]:before
 {
   border-bottom: rgba(128,128,128,0.6) 1px dashed;
 }
 
-tree:not([filterHidden="false"]) div[itemMasterContainer]:last-child > [itemContainer]:before
+tree div[itemMasterContainer]:last-child > [itemContainer]:before
 {
   border-bottom: rgba(128,128,128,0.6) 1px dashed;
   border-left : rgba(128,128,128,0.6) 1px dashed;
diff --git a/src/components/tree/tree.template.html b/src/components/tree/tree.template.html
index c0d71c6a22f72e3e3d123a2df707ba90a84d5adf..0b7192da4ef22a9000b7e04f0e342054c0c89490 100644
--- a/src/components/tree/tree.template.html
+++ b/src/components/tree/tree.template.html
@@ -1,5 +1,4 @@
 <div
-  [hidden] = "!visibilityOnFilter"
   (mouseleave)="mouseleavetree.emit({inputItem:inputItem,node:this});handleEv($event)"
   (mouseenter)="mouseentertree.emit({inputItem:inputItem,node:this});handleEv($event)"
   (click)="mouseclicktree.emit({inputItem:inputItem,node:this});handleEv($event)"
@@ -17,12 +16,13 @@
     </span>
   </div>
   <tree
-    *ngFor = "let child of findChildren(inputItem)"
+    *ngFor = "let child of (findChildren(inputItem) | treeSearch : searchFilter : findChildren )"
     [hidden] = "!childrenExpanded"
     [childrenExpanded] = "childrenExpanded"
     [inputItem] = "child"
     [renderNode]="renderNode"
     [searchFilter]="searchFilter"
+    [findChildren] = "findChildren"
     (mouseentertree)="mouseentertree.emit($event)"
     (mouseleavetree)="mouseleavetree.emit($event)"
     (mouseclicktree)="mouseclicktree.emit($event)">
diff --git a/src/index.html b/src/index.html
index d24b57232a80f827db473d8fa9b813d4345cd970..ec4d02f37491659b0cfa03110309b0073a4388d9 100644
--- a/src/index.html
+++ b/src/index.html
@@ -1,131 +1,10 @@
 <!doctype html>
 <html>
   <head>
-    <style>  
-      markdown-dom pre code
-      {
-        white-space:pre;
-      }
-    </style>
-    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
-    <!-- needed for nehuba container only  -->
-    <style>
-      html
-      {
-        width:100%;
-        height:100%;
-      }
-      body
-      {
-        width:100%;
-        height:100%;
-        margin:0;
-        border:0;
+    <link rel = "stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
+    <link rel = "stylesheet" href = "extra_styles.css">
+    <link rel = "stylesheet" href = "plugin_styles.css">
 
-        /* required for glyphicon tooltip directives */
-        overflow:hidden;
-      }
-      div.scale-bar-container
-      {
-        text-align: center;
-        background-color: rgba(0,0,0,.3);
-        position: absolute;
-        left: 1em;
-        bottom: 1em;
-        padding: 2px;
-        font-weight: 700;
-        pointer-events: none;
-      }
-
-      div.scale-bar
-      {
-        min-height: 1ex;
-        background-color: #fff;
-        padding: 0;
-        margin: 0;
-        margin-top: 2px;
-      }
-      div.neuroglancer-rendered-data-panel
-      {
-        position:relative;
-      }
-
-      ul#statusContainer
-      {
-        display:none;
-      }
-
-      .inputSearchContainer
-      {
-        background:none;
-        box-shadow:none;
-        border:none;
-        /* width:25em; */
-        max-width:999999px;
-      }
-      .inputSearchContainer .popover-arrow
-      {
-        display:none;
-      }
-
-      .inputSearchContainer .popover-content.popover-body
-      {
-        padding:0;
-        max-width:999999px;
-      }
-
-      .mute-text
-      {
-        opacity:0.8;
-      }
-
-      div.scale-bar-container
-      {
-        font-weight:500;
-        color: #1a1a1a;
-        background-color:hsla(0,0%,80%,0.5);
-      }
-
-      label.perspective-panel-show-slice-views
-      {
-        visibility: hidden;
-      }
-
-      label.perspective-panel-show-slice-views:hover
-      {
-        text-decoration: underline
-      }
-
-      [darktheme="false"] .neuroglancer-panel
-      {
-        border:2px solid rgba(255,255,255,0.9);
-      }
-
-      [darktheme="true"] .neuroglancer-panel
-      {
-        border:2px solid rgba(30,30,30,0.9);
-      }
-
-      label.perspective-panel-show-slice-views:before
-      {
-        margin-left: .2em;
-        content: "show / hide frontal octant";
-        visibility: visible;
-        pointer-events: all;
-        color: #337ab7;
-      }
-      
-      [darktheme="true"] .scale-bar-container
-      {
-        color:#f2f2f2;
-        background-color:hsla(0,0%,60%,0.2);
-      }
-
-      span.regionSelected
-      {
-        color : #dbb556
-      }
-    </style>
     <!-- <link rel = "stylesheet" href = "styles.css" /> -->
   </head>
   <body>
diff --git a/src/main.module.ts b/src/main.module.ts
index cfa7c2680a32241cb3171243c9745cb48c80a151..e873454c5700dfbeb543dbc3f2eab5873ab6ba37 100644
--- a/src/main.module.ts
+++ b/src/main.module.ts
@@ -5,13 +5,11 @@ import { LayoutModule } from "./layouts/layout.module";
 import { AtlasViewer } from "./atlasViewer/atlasViewer.component";
 import { StoreModule } from "@ngrx/store";
 import { viewerState, dataStore,spatialSearchState,uiState } from "./services/stateStore.service";
-import { AtlasBanner } from "./ui/banner/banner.component";
 import { GetNamesPipe } from "./util/pipes/getNames.pipe";
 import { CommonModule } from "@angular/common";
 import { GetNamePipe } from "./util/pipes/getName.pipe";
 import { FormsModule } from "@angular/forms";
 
-import { PopoverModule } from 'ngx-bootstrap/popover'
 import { AtlasViewerDataService } from "./atlasViewer/atlasViewer.dataService.service";
 import { WidgetUnit } from "./atlasViewer/widgetUnit/widgetUnit.component";
 import { WidgetServices } from './atlasViewer/widgetUnit/widgetService.service'
@@ -23,6 +21,8 @@ import { AtlasViewerURLService } from "./atlasViewer/atlasViewer.urlService.serv
 import { ToastComponent } from "./components/toast/toast.component";
 import { GetFilenameFromPathnamePipe } from "./util/pipes/getFileNameFromPathName.pipe";
 import { FilterNameBySearch } from "./util/pipes/filterNameBySearch.pipe";
+import { AtlasViewerAPIServices } from "./atlasViewer/atlasViewer.apiService.service";
+import { PluginUnit } from "./atlasViewer/pluginUnit/pluginUnit.component";
 
 @NgModule({
   imports : [
@@ -34,7 +34,6 @@ import { FilterNameBySearch } from "./util/pipes/filterNameBySearch.pipe";
     
     ModalModule.forRoot(),
     TooltipModule.forRoot(),
-    PopoverModule.forRoot(),
     StoreModule.forRoot({
       viewerState ,
       dataStore ,
@@ -44,9 +43,9 @@ import { FilterNameBySearch } from "./util/pipes/filterNameBySearch.pipe";
   ],
   declarations : [
     AtlasViewer,
-    AtlasBanner,
     WidgetUnit,
     ModalUnit,
+    PluginUnit,
 
     /* directives */
     GlyphiconTooltipScreenshotDirective,
@@ -61,18 +60,20 @@ import { FilterNameBySearch } from "./util/pipes/filterNameBySearch.pipe";
     GetNamesPipe,
     GetNamePipe,
     GetFilenameFromPathnamePipe,
-    FilterNameBySearch
+    FilterNameBySearch,
   ],
   entryComponents : [
     WidgetUnit,
     ModalUnit,
     ToastComponent,
+    PluginUnit,
   ],
   providers : [
     AtlasViewerDataService,
     AtlasViewerDataService,
     WidgetServices,
-    AtlasViewerURLService
+    AtlasViewerURLService,
+    AtlasViewerAPIServices
   ],
   bootstrap : [
     AtlasViewer
diff --git a/src/plugin_examples/jugex/manifest.json b/src/plugin_examples/jugex/manifest.json
new file mode 100644
index 0000000000000000000000000000000000000000..05ead3b046bc2b1a039bc149dad4a98d8bf64119
--- /dev/null
+++ b/src/plugin_examples/jugex/manifest.json
@@ -0,0 +1 @@
+{"name":"fzj.hb.jugex","type":"plugin","templateURL":"http://localhost:10080/jugex/template.html","scriptURL":"http://localhost:10080/jugex/script.js"}
diff --git a/src/plugin_examples/jugex/newscript.js b/src/plugin_examples/jugex/newscript.js
new file mode 100644
index 0000000000000000000000000000000000000000..22a6ab15d44eca451412ab9a38985860d5bbca60
--- /dev/null
+++ b/src/plugin_examples/jugex/newscript.js
@@ -0,0 +1,803 @@
+(()=>{
+  const PLUGIN_NAME = `fzj.hb.JuGEx`
+  const DOM_PARSER = new DOMParser()
+  const MIN_CHAR = 3
+  const URL_BASE = 'http://medpc055.ime.kfa-juelich.de:8003'
+
+  class HoverRegionSelectorComponent extends HTMLElement{
+    constructor(){
+      super()
+
+      this.template = 
+      `
+        <div class = "input-group">
+          <input value = "${this.selectedRegion ? this.selectedRegion.name : ''}" class = "form-control" placeholder = "" readonly = "readonly" type = "text" region />
+          <span class = "input-group-btn">
+            <div class = "btn btn-default" editRegion>
+              <span class = "glyphicon glyphicon-edit"></span>
+            </div>
+          </span>
+        </div>
+      `
+
+      this.elTemplate = DOM_PARSER.parseFromString(this.template,'text/html') // or use innerHTML... whichever suits you
+
+      this.elTemplate2 = document.createElement('div')
+      this.elTemplate2.innerHTML = this.template
+
+      this.renderedFlag = false
+      this.listening = true
+      this.selectedRegion = null
+      this.shutdownHooks = []
+
+      this.rootChild = document.createElement('div')
+      this.appendChild(this.rootChild)
+      this.firstrender = true
+
+      this.init()
+      window.pluginControl[PLUGIN_NAME].onShutdown((this.onShutdown).bind(this))
+    }
+
+    /* class method */
+    /* connectedCallback can get called multiple times during the lifetime of a widget.
+    most prominently, when the user chooses to dock the widget, or minimise the widget. */
+    connectedCallback(){
+      this.render()
+      this.attachEventListeners()
+    }
+
+    /* class method */
+    /* ditto, see above */
+    disconnectedCallback(){
+      this.unattachEventListeners()
+    }
+
+    /* in this example, the init funciton attaches any permanent listeners, such as, in this
+    case, mouseovernehuba event stream */
+    init(){
+      this.mouseOverNehuba = window.viewerHandle.mouseOverNehuba.filter(()=>this.listening).subscribe(ev=>{
+        this.selectedRegion = ev.foundRegion
+        this.render()
+        this.attachEventListeners()
+      })
+    }
+
+    /* cleaning up when the user permanently closes the widget */
+    onShutdown(){
+      this.mouseOverNehuba.unsubscribe()
+    }
+
+    render(){
+      if(!this.firstrender){
+        this.unattachEventListeners()
+        this.firstrender = false
+      }
+      while(this.rootChild.lastChild){
+        this.rootChild.removeChild(this.rootChild.lastChild)
+      }
+
+      this.template = 
+      `
+        <div class = "input-group">
+          <input value = "${this.selectedRegion ? this.selectedRegion.name : ''}" class = "form-control" placeholder = "" readonly = "readonly" type = "text" region />
+          <span class = "input-group-btn">
+            <div class = "btn btn-default" editRegion>
+              <span class = "glyphicon glyphicon-edit"></span>
+            </div>
+          </span>
+        </div>
+      `
+      this.elTemplate2.innerHTML = this.template
+      this.rootChild.appendChild(this.elTemplate2)
+    }
+
+    clearAndRelisten(ev){
+      this.listening = true
+      this.selectedRegion = null
+      this.render()
+      this.attachEventListeners()
+    }
+
+    attachEventListeners(){
+      this.rootChild.querySelector('div[editRegion]').addEventListener('click',(this.clearAndRelisten).bind(this))
+    }
+
+    unattachEventListeners(){
+      this.rootChild.querySelector('div[editRegion]').addEventListener('click',(this.clearAndRelisten).bind(this))
+    }
+  }
+
+  class DismissablePill extends HTMLElement{
+    constructor(){
+      super()
+      this.name = ``
+      this.template = 
+      `
+      <span class = "label label-default">
+        <span pillName>${this.name}</span>
+        <span class = "glyphicon glyphicon-remove" pillRemove></span>
+      </span>
+      `
+    }
+
+    render(){
+      this.template = 
+      `
+      <span class = "label label-default">
+        <span pillName>${this.name}</span>
+        <span class = "glyphicon glyphicon-remove" pillRemove></span>
+      </span>
+      `
+      this.elTemplate = document.createElement('span')
+      // this.elTemplate = DOM_PARSER.parseFromString(this.template,'text/html')
+      this.elTemplate.innerHTML = this.template
+
+      while(this.lastChild){
+        this.removeChild(this.lastChild)
+      }
+      this.appendChild(this.elTemplate)
+    }
+
+    connectedCallback(){
+      this.render()
+      this.attachEventListeners()
+    }
+
+    disconnectedCallback(){
+      this.unattachEventListeners()
+    }
+
+    attachEventListeners(){
+      this.querySelector('span[pillRemove]').addEventListener('click',(this.dismissPill).bind(this))
+    }
+
+    unattachEventListeners(){
+      this.querySelector('span[pillRemove]').removeEventListener('click',(this.dismissPill).bind(this))
+    }
+
+    dismissPill(){
+      this.onRemove(this.name)
+      this.remove()
+    }
+    
+    /* needs to be overwritten by parent, if parent needs to listen to the on remove event */
+    onRemove(){
+
+    }
+  }
+
+  class WebjugexGeneComponent extends HTMLElement{
+    constructor(){
+      super()
+      this.arrDict = []
+      this.autocompletesuggestion = []
+      this.selectedGenes = []
+      this.template = 
+      `
+        <div class = "input-group">
+          <input geneInputBox type = "text" class = "form-control" placeholder = "Enter gene of interest ..." />
+          <input geneImportInput class = "hidden" type = "file" />
+          <span class = "input-group-btn">
+            <div geneAdd class = "btn btn-default" title = "Add a gene">Add</div>
+            <div geneImport class = "btn btn-default" title = "Import a CSV file">Import</div>
+            <div geneExport class = "btn btn-default" title = "Export selected genes into a CSV file">Export</div>
+          </span>
+        </div>
+      `
+
+      window.pluginControl[PLUGIN_NAME].onShutdown((this.unloadExternalResources).bind(this))
+      this.firstrender = true
+    }
+
+    connectedCallback(){
+      if(this.firstrender){
+
+        this.elTemplate = document.createElement('div')
+        this.elTemplate.innerHTML = this.template
+        this.rootChild = document.createElement('div')
+        this.appendChild(this.rootChild)
+        this.rootChild.appendChild(this.elTemplate)
+        this.init()
+        this.firstrender = false
+      }
+      // this.render()
+      // this.attachEventListeners()
+      /* see below */
+    }
+
+    disconnectedCallback(){
+      // this.unattachEventListeners()
+      /* see below */
+    }
+
+    // render(){
+    //   while(this.lastChild){
+    //     this.removeChild(lastChild)
+    //   }
+    //   this.appendChild(this.elTemplate)
+    // }
+
+    /* in special circumstances, where the view does not change too much, but there are numerous
+    eventlisteners, it maybe more efficient to only attach event listener once,  */
+    init(){
+      
+      this.elGeneInputBox = this.rootChild.querySelector('input[geneInputBox]')
+      this.elGeneImportInput = this.rootChild.querySelector('input[geneImportInput]')
+      this.elGeneAdd = this.rootChild.querySelector('div[geneAdd]')
+      this.elGeneImport = this.rootChild.querySelector('div[geneImport]')
+      this.elGeneExport = this.rootChild.querySelector('div[geneExport]')
+      
+      const importGeneList = (file) => {
+        const csvReader = new FileReader()
+        csvReader.onload = (ev)=>{
+          const csvRaw = ev.target.result
+          this.selectedGenes.splice(0,this.selectedGenes.length)
+          csvRaw.split(/\r|\r\n|\n|\t|\,|\;/).forEach(gene=>{
+              if(gene.length > 0)
+              this.addGene(gene)
+          })
+        }
+        csvReader.readAsText(file,'utf-8')
+      }
+
+      this.elGeneImportInput.addEventListener('change',(ev)=>{
+        importGeneList(ev.target.files[0])
+      })
+
+      this.elGeneImport.addEventListener('click',()=>{
+        this.elGeneImportInput.click()
+      })
+      this.elGeneExport.addEventListener('click',()=>{
+        const exportGeneList = 'data:text/csv;charset=utf-8,'+this.selectedGenes.join(',')
+        const exportGeneListURI = encodeURI(exportGeneList)
+        const dlExportGeneList = document.createElement('a')
+        dlExportGeneList.setAttribute('href',exportGeneListURI)
+        document.body.appendChild(dlExportGeneList)
+        const date = new Date()
+        dlExportGeneList.setAttribute('download',`exported_genelist_${''+date.getFullYear()+(date.getMonth()+1)+date.getDate()+'_'+date.getHours()+date.getMinutes()}.csv`)
+        dlExportGeneList.click()
+        document.body.removeChild(dlExportGeneList)
+      })
+      this.elGeneAdd.addEventListener('click',()=>{
+        if(this.autocompleteSuggestions.length > 0 && this.elGeneInputBox.value.length >= MIN_CHAR)
+        this.addGene(this.autocompleteSuggestions[0])
+      })
+
+      this.elGeneInputBox.addEventListener('dragenter',(ev)=>{
+        this.elGeneInputBox.setAttribute('placeholder','Drop file here to be uploaded')
+      })
+
+      this.elGeneInputBox.addEventListener('dragleave',(ev)=>{
+        this.elGeneInputBox.setAttribute('placeholder','Enter gene of interest ... ')
+      })
+
+      this.elGeneInputBox.addEventListener('drop',(ev)=>{
+        ev.preventDefault()
+        ev.stopPropagation()
+        ev.stopImmediatePropagation()
+        this.elGeneInputBox.setAttribute('placeholder','Enter gene of interest ... ')
+        //ev.dataTransfer.files[0]
+      })
+
+      this.elGeneInputBox.addEventListener('dragover',(ev)=>{
+        ev.preventDefault()
+        ev.stopPropagation()
+        ev.stopImmediatePropagation()
+      })
+
+      this.elGeneInputBox.addEventListener('keydown',(ev)=>{
+        ev.stopPropagation()
+        ev.stopImmediatePropagation()
+        if(ev.key=='Enter') this.elGeneAdd.click()
+      })
+
+      Promise.all([
+        this.loadExternalResources(),
+        fetch(URL_BASE).then(txt=>txt.json())
+      ])
+        .then(arr=>{
+          this.arrDict = arr[1]
+
+          console.log('attaching autocomplete')
+
+          this.autocompleteInput = new autoComplete({
+            selector : this.elGeneInputBox,
+            delay : 0,
+            minChars : MIN_CHAR,
+            cache : false,
+            source : (term,suggest)=>{
+              const searchTerm = new RegExp('^'+term,'gi')
+              this.autocompleteSuggestions = this.arrDict.filter(dict=>searchTerm.test(dict))
+              suggest(this.autocompleteSuggestions)
+            },
+            onSelect : (e,term,item)=>{
+              this.addGene(term)
+            }
+          })
+        })
+        .catch(err=>{
+          console.error('loading external resources failed ... ',err)
+          // console.log('failed to fetch full list of genes... using limited list of genes instead ...',e)
+          // this.arrDict = ["ADRA2A", "AVPR1B", "CHRM2", "CNR1", "CREB1", "CRH", "CRHR1", "CRHR2", "GAD2", "HTR1A", "HTR1B", "HTR1D", "HTR2A", "HTR3A", "HTR5A", "MAOA", "PDE1A", "SLC6A2", "SLC6A4", "SST", "TAC1", "TPH1", "GPR50", "CUX2", "TPH2"]
+        })
+    }
+
+    loadExternalResources(){
+      return new Promise((rs,rj)=>Promise.all([
+        new Promise((resolve,reject)=>{
+          this.autoCompleteCss = document.createElement('link')
+          this.autoCompleteCss.type = 'text/css'
+          this.autoCompleteCss.rel = 'stylesheet'
+          this.autoCompleteCss.onload = () => resolve()
+          this.autoCompleteCss.onerror = (e) => reject(e)
+          this.autoCompleteCss.href = '/res/css/js-autocomplete.min.css'
+          document.head.appendChild(this.autoCompleteCss)
+        }),
+        new Promise((resolve,reject)=>{
+          this.autoCompleteJs = document.createElement('script')
+          this.autoCompleteJs.onload = () => resolve()
+          this.autoCompleteJs.onerror = (e) => reject(e)
+          this.autoCompleteJs.src = '/res/js/js-autocomplete.min.js'
+          document.head.appendChild(this.autoCompleteJs)
+        })
+      ])
+      .then(()=>rs())
+      .catch(e=>rj(e))
+    )}
+
+    unloadExternalResources(){
+      document.head.removeChild(this.autoCompleteJs)
+      document.head.removeChild(this.autoCompleteCss)
+    }
+
+    addGene(gene){
+      const pill = document.createElement('dismissable-pill-card')
+      pill.onRemove = (name) => 
+        this.selectedGenes.splice(this.selectedGenes.indexOf(name),1)
+
+      pill.name = gene
+      this.rootChild.appendChild(pill)
+      this.selectedGenes.push(gene)
+      this.elGeneInputBox.value = ''
+      this.elGeneInputBox.blur()
+      this.elGeneInputBox.focus()
+    }
+  }
+
+  class WebjugexSearchComponent extends HTMLElement{
+    constructor(){
+      super()
+      this.template = 
+      `
+      
+      <div class = "row">
+        <div class = "col-md-12">
+          <small>
+            Find a set of differentially expressed genes between two user defined volumes of interest based on JuBrain maps.
+            The tool downloads expression values of user specified sets of genes from Allen Brain API.
+            Then, it uses zscores to find which genes are expressed differentially between the user specified regions of interests.
+            After the analysis is finished, the genes and their calculated p values are displayed. There is also an option of downloading the gene names and their p values
+            and the roi coordinates used in the analysis.
+            Please select two regions of interest, and at least one gene :
+          </small>
+        </div>
+        <div class = "col-md-12">
+          <hover-region-selector-card area1></hover-region-selector-card>
+        </div>
+        <div class = "col-md-12">
+          <hover-region-selector-card area2></hover-region-selector-card>
+        </div>
+        <div class = "col-md-12">
+        <div class = "input-group">
+          <span class = "input-group-addon">
+            Threshold
+          </span>
+          <input value = "0.20" class = "form-control" type = "range" min = "0" max = "1" step = "0.01" threshold />
+          <span class = "input-group-addon" thresholdValue>
+          0.20
+          </span>
+        </div>
+        <div class="input-group">
+          <input type="checkbox" probemode /> Single Probe Mode
+        </div>
+      </div>
+      <div class = "row">
+        <div class = "col-md-12">
+          <fzj-xg-webjugex-gene-card>
+          </fzj-xg-webjugex-gene-card>
+        </div>
+      </div>
+      <div class = "row">
+        <div class = "col-md-12">
+          <div class = "btn btn-default btn-block" analysisSubmit>
+            Start differential analysis
+          </div>
+        </div>
+      </div>
+      `
+      this.mouseEventSubscription = this.rootChild = this.threshold = this.elArea1 = this.elArea2 = null
+      this.selectedGenes = []
+      this.firstrender = true
+
+    }
+
+    connectedCallback(){
+      if(this.firstrender){
+        this.init()
+        this.firstrender = false
+      }
+    }
+
+    init(){
+      // this.elTemplate = DOM_PARSER.parseFromString(this.template,'text/html')
+      this.elTemplate = document.createElement('div')
+      this.elTemplate.innerHTML = this.template
+      this.appendChild(this.elTemplate)
+
+      this.elArea1 = this.querySelector('hover-region-selector-card[area1]')
+      this.elArea2 = this.querySelector('hover-region-selector-card[area2]')
+      this.elArea1.listening = true
+      this.elArea2.listening = false
+      this.probemodeval = false
+
+      this.elGenesInput = this.querySelector('fzj-xg-webjugex-gene-card')
+
+      this.elAnalysisSubmit = this.querySelector('div[analysisSubmit]')
+      this.elAnalysisSubmit.style.marginBottom = '20px'
+      this.elAnalysisSubmit.addEventListener('click',()=>this.analysisGo())
+
+      this.elThreshold = this.querySelector('input[threshold]')
+      const elThresholdValue = this.querySelector('span[thresholdValue]')
+      this.elThreshold.addEventListener('input',(ev)=> elThresholdValue.innerHTML = parseFloat(this.elThreshold.value).toFixed(2) )
+
+      this.onViewerClick()
+
+      window.pluginControl[PLUGIN_NAME].onShutdown(()=>{
+        this.mouseEventSubscription.unsubscribe()
+      })
+    }
+
+    onViewerClick(){
+      this.mouseEventSubscription = window.viewerHandle.mouseEvent.filter(ev=>ev.eventName=='click').subscribe(ev=>{
+          if(this.elArea1.listening && this.elArea2.listening){
+              this.elArea1.listening = false
+          }
+          else if(this.elArea2.listening){
+              this.elArea2.listening = false
+          }
+          else if(this.elArea1.listening){
+              if(this.elArea2.selectedRegion == null){
+                  this.elArea1.listening = false
+                  this.elArea2.listening = true
+              }
+              else if(this.elArea2.selectedRegion != null){
+                  this.elArea1.listening = false
+              }
+          }
+        })
+    }
+
+    analysisGo(){
+      /* test for submit conditions */
+      if(this.elArea1.selectedRegion == null || this.elArea2.selectedRegion == null || this.elGenesInput.selectedGenes.length < 1){
+        const resultCard = document.createElement('fzj-xg-webjugex-result-failure-card')
+        container.appendChild(resultCard)
+        let e = 'Error: We need '
+        if(this.elArea1.selectedRegion == null || this.elArea2.selectedRegion == null) e += 'both areas to be defined and '
+        if(this.elGenesInput.selectedGenes.length < 1) e += 'atleast one gene'
+        else e = e.substr(0, 40)
+        e += '.'
+        resultCard.panelBody.innerHTML = e
+        return
+      }
+      console.log(this.elArea1.selectedRegion,this.elArea2.selectedRegion,this.elArea1.selectedRegion.PMapURL,this.elArea2.selectedRegion.PMapURL,this.elThreshold.value,this.elGenesInput.selectedGenes)
+      const region1 = Object.assign({},this.elArea1.selectedRegion,{url:this.elArea1.selectedRegion.PMapURL})
+      const region2 = Object.assign({},this.elArea2.selectedRegion,{url:this.elArea2.selectedRegion.PMapURL})
+      this.sendAnalysis({
+        area1 : region1,
+        area2 : region2,
+        threshold : this.elThreshold.value,
+        selectedGenes : this.elGenesInput.selectedGenes,
+        mode : this.querySelector('input[probemode]').checked
+      })
+    }
+
+    sendAnalysis(analysisInfo){
+      /* to be overwritten by parent class */
+    }
+  }
+  
+  /* custom class for analysis-card */
+  class WebJuGExAnalysisComponent extends HTMLElement{
+    constructor(){
+      super()
+      this.template = ``
+      this.analysisObj = {}
+      this.status = 'pending'
+    }
+
+    connectedCallback(){
+      this.render()
+      this.panelHeader = this.querySelector('[panelHeader]')
+    }
+
+    render(){
+
+      this.template =
+      `
+        <div class = "row">
+          <div class="progress">
+            <div class="progress-bar progress-bar-striped active" style="width:100%"></div>
+          </div>
+        </div>
+      `
+      this.innerHTML = this.template
+    }
+  }
+
+  
+  const searchCard = document.querySelector('fzj-xg-webjugex-search-card')
+  const container = document.getElementById('fzj.xg.webjugex.container')
+  const parseContentToCsv = (content)=>{
+      const CSVContent = 'data:text/csv;charset=utf-8,'+content
+      const CSVURI = encodeURI(CSVContent)
+      const domDownload = document.createElement('a')
+      domDownload.setAttribute('href',CSVURI)
+      return domDownload
+  }
+  const createRow = ()=>{
+      const domDownload = document.createElement('div')
+      domDownload.style.display = 'flex'
+      domDownload.style.flexDirection = 'row'
+      const col1 = document.createElement('div')
+      const col2 = document.createElement('div')
+      col2.style.flex = col1.style.flex = '0 0 50%'
+      domDownload.appendChild(col1)
+      domDownload.appendChild(col2)
+      return [domDownload,col1,col2]
+  }
+  /* custom class for analysis-card */
+
+
+  class WebJuGExResultSuccessComponent extends HTMLElement{
+      constructor(){
+          super()
+          this.template = ``
+          this.resultObj = {}
+          this.pvalString = ''
+          this.areaString = ''
+          this.status = 'pending'
+          this.firstrender = true
+      }
+
+      connectedCallback(){
+        
+        if(this.firstrender){
+          this.childRoot = document.createElement('div')
+          this.appendChild(this.childRoot)
+          this.render()
+
+          this.panelHeader = this.childRoot.querySelector('[panelHeader]')
+          this.panelBody = this.childRoot.querySelector('[panelBody]')
+          this.panelHeader.addEventListener('click',()=>{
+            this.uiTogglePanelBody()
+          })
+          this.firstrender = false
+        }
+      }
+
+      uiTogglePanelBody(){
+          if(/hidden/.test(this.panelBody.className)){
+              this.panelBody.classList.remove('hidden')
+          }else{
+              this.panelBody.classList.add('hidden')
+          }
+      }
+
+      render(){
+          this.template =
+          `
+          <div class = "row">
+          <div class = "panel panel-success">
+          <div class = "btn btn-default btn-block panel-heading" panelHeader>
+          <span class="glyphicon glyphicon-ok"></span> Request completed! <u> Details below.</u>
+          </div>
+          <div class = "panel-body hidden" panelBody>
+          </div>
+          <div class = "panel-footer hidden" panelFooter>
+          </div>
+          </div>
+          </div>
+          `
+          this.childRoot.innerHTML = this.template
+      }
+  }
+
+  class WebJuGExResultFailureComponent extends HTMLElement{
+      constructor(){
+          super()
+          this.template = ``
+          this.resultObj = {}
+          this.pvalString = ''
+          this.areaString = ''
+          this.status = 'pending'
+          this.firstrender = true
+      }
+
+      connectedCallback(){
+          // const shadowRoot = this.attachShadow({mode:'open'})
+          if(this.firstrender){
+
+            this.childRoot = document.createElement('div')
+            this.appendChild(this.childRoot)
+            this.render()
+  
+            this.panelHeader = this.childRoot.querySelector('[panelHeader]')
+            this.panelBody = this.childRoot.querySelector('[panelBody]')
+            this.panelHeader.addEventListener('click',()=>{
+                                                  this.uiTogglePanelBody()
+                                              })
+            this.firstrender = false
+          }
+      }
+
+      uiTogglePanelBody(){
+          if(/hidden/.test(this.panelBody.className)){
+              this.panelBody.classList.remove('hidden')
+          }else{
+              this.panelBody.classList.add('hidden')
+          }
+      }
+
+      render(){
+          this.template =
+          `
+          <div class = "row">
+          <div class = "panel panel-danger">
+          <div class = "btn btn-default btn-block panel-heading" panelHeader>
+          <span class="glyphicon glyphicon-remove"></span> Error. Check below.
+          </div>
+          <div class = "panel-body hidden" panelBody>
+          </div>
+          <div class = "panel-footer hidden" panelFooter>
+          </div>
+          </div>
+          </div>
+          `
+          this.childRoot.innerHTML = this.template
+      }
+  }
+
+  searchCard.sendAnalysis = (analysisInfo) => {
+
+    console.log(analysisInfo)
+
+      const analysisCard = document.createElement('fzj-xg-webjugex-analysis-card')
+      analysisCard.analysisObj = analysisInfo
+      container.appendChild(analysisCard)
+      const headers = new Headers()
+      headers.append('Content-Type','application/json')
+      const request = new Request(`${URL_BASE}/jugex`,{
+        method : 'POST',
+        headers : headers,
+        mode : 'cors',
+        body : JSON.stringify(analysisInfo)
+      })
+      fetch(request)
+        .then(resp => {
+          if (resp.ok){
+            return Promise.resolve(resp)
+          }
+          else {
+            return new Promise((resolve,reject)=>{
+              resp.text()
+                .then(text=>reject(text))
+            })
+          }
+        })
+      .then(resp=>resp.text())
+      .then(text=>{
+        container.removeChild(analysisCard)
+        const resultCard = document.createElement('fzj-xg-webjugex-result-success-card')
+        container.appendChild(resultCard)
+        const date = new Date()
+        const dateDownload = ''+date.getFullYear()+(date.getMonth()+1)+date.getDate()+'_'+date.getHours()+':'+date.getMinutes()
+        resultCard.panelHeader.innerHTML += '('+dateDownload+')'
+        resultCard.resultObj = JSON.parse(text)
+        extension = createRow()
+        extension[0].style.order = -1
+        if(resultCard.resultObj.length == 3){
+            extension[1].innerHTML = 'Probe ids'
+        }
+        else if(resultCard.resultObj.length == 2){
+            extension[1].innerHTML = 'Gene Symbol'
+        }
+        extension[1].style.fontWeight = 900
+        extension[2].innerHTML = 'Pval'
+        extension[2].style.fontWeight = 900
+        resultCard.panelBody.style.maxHeight = '400px'
+        resultCard.panelBody.style.overflowY = 'scroll'
+        resultCard.panelBody.appendChild(extension[0])
+        let count = 0
+        for(let key in resultCard.resultObj[1]){
+            count = count+1
+        }
+        for(let key in resultCard.resultObj[1]){
+            resultCard.pvalString += [key, resultCard.resultObj[1][key]].join(',') + '\n'
+        }
+        if (count < 2){
+            for (let key in resultCard.resultObj[1]){
+                extension = createRow()
+                extension[0].style.order = Number(resultCard.resultObj[1][key]) ? Math.round(Number(resultCard.resultObj[1][key])*1000) : 1000
+                extension[1].innerHTML = key
+                extension[2].innerHTML = resultCard.resultObj[1][key]
+                resultCard.panelBody.appendChild(extension[0])
+            }
+        }
+        else{
+            let v = 0
+            for(let key in resultCard.resultObj[1]){
+                extension = createRow()
+                extension[0].style.order = Number(resultCard.resultObj[1][key]) ? Math.round(Number(resultCard.resultObj[1][key])*1000) : 1000
+                if(v == 0  || v == count-1){
+                    extension[1].innerHTML = key
+                    extension[2].innerHTML = resultCard.resultObj[1][key]
+                }
+                else if (v == 1 || v == 2){
+                    extension[1].innerHTML = '...'
+                    extension[2].innerHTML = '...'
+                }
+                v = v+1
+                resultCard.panelBody.appendChild(extension[0])
+            }
+        }
+        resultCard.areaString = 'ROI, x, y, z, '
+        if(resultCard.resultObj.length == 3){
+            resultCard.areaString +=  resultCard.resultObj[2]+'\n'
+        }
+        else{
+            for(let key in resultCard.resultObj[1]){
+                resultCard.areaString += key+','
+            }
+            resultCard.areaString = resultCard.areaString.slice(0, -1)
+            resultCard.areaString += '\n'
+        }
+        for(let key in resultCard.resultObj[0]){
+            for(let i in resultCard.resultObj[0][key]){
+                resultCard.areaString += key+','+resultCard.resultObj[0][key][i]['xyz'].join(',')+','+resultCard.resultObj[0][key][i]['winsorzed_mean']+'\n'
+            }
+        }
+
+        const domDownloadPVal = parseContentToCsv(resultCard.pvalString)
+        domDownloadPVal.innerHTML = 'Download Pvals of genes ('+dateDownload+')'
+        domDownloadPVal.setAttribute('download','PVal.csv')
+        resultCard.panelBody.append(domDownloadPVal)
+        linebreak = document.createElement("br")
+        resultCard.panelBody.append(linebreak)
+        const domDownloadArea = parseContentToCsv(resultCard.areaString)
+        domDownloadArea.innerHTML = 'Download sample coordinates ('+dateDownload+')'
+        domDownloadArea.setAttribute('download',`SampleCoordinates.csv`)
+        domDownloadArea.style.order = -3
+        resultCard.panelBody.append(domDownloadArea)
+      })
+      .catch(e=>{
+        console.log('Here 2')
+        container.removeChild(analysisCard)
+        const resultCard = document.createElement('fzj-xg-webjugex-result-failure-card')
+        container.appendChild(resultCard)
+        console.log('error',e)
+        resultCard.panelBody.innerHTML = e
+      })
+  }
+
+  customElements.define('hover-region-selector-card', HoverRegionSelectorComponent)
+  customElements.define('fzj-xg-webjugex-analysis-card',WebJuGExAnalysisComponent)
+  
+  customElements.define('fzj-xg-webjugex-result-success-card',WebJuGExResultSuccessComponent)
+  customElements.define('fzj-xg-webjugex-result-failure-card',WebJuGExResultFailureComponent)
+  
+  customElements.define('dismissable-pill-card',DismissablePill)
+  
+  customElements.define('fzj-xg-webjugex-gene-card',WebjugexGeneComponent)
+  customElements.define('fzj-xg-webjugex-search-card',WebjugexSearchComponent)
+})()
diff --git a/src/plugin_examples/jugex/script.js b/src/plugin_examples/jugex/script.js
new file mode 100644
index 0000000000000000000000000000000000000000..6a88f9f2c1f9077d6e715967258fad7159554e6d
--- /dev/null
+++ b/src/plugin_examples/jugex/script.js
@@ -0,0 +1,840 @@
+(() => {
+    // // const landmarkService = interactiveViewer.experimental.landmarkService
+    const code = () => {
+
+        const register = (tag,classname)=>{
+
+            try{
+                customElements.define(tag, classname)
+            }catch(e){
+                console.warn(tag + ' already registered',e)
+            }
+        }
+
+        
+        const basePath = 'http://medpc055.ime.kfa-juelich.de:5080/plugins/webjugex/'
+
+        const backendBasePath = 'http://medpc055.ime.kfa-juelich.de:8005/'
+
+        /* components like this are reusable. */
+        class HoverRegionSelectorComponent extends HTMLElement {
+
+            constructor() {
+                super()
+
+                this.template =
+                    `
+                    <div class = "input-group">
+                    <input class = "form-control" placeholder = "" readonly = "readonly" type = "text" region>
+                    <span class = "input-group-btn">
+                    <div class = "btn btn-default" editRegion>
+                    <span class = "glyphicon glyphicon-edit"></span>
+                    </div>
+                    </span>
+                    </div>
+                    `
+                this.listening = true
+                this.selectedRegion = null
+                this.shutdownHooks = []
+            }
+
+            connectNehubaHooks() {
+                const mouseOverNehuba = window.interactiveViewer.viewerHandle.mouseOverNehuba
+                    .subscribe(ev => {
+                        if(!this.listening)
+                            return
+                            
+                        this.selectedRegion = ev ? ev : null
+                        this.render()
+                    })
+
+                this.shutdownHooks.push(() => mouseOverNehuba.unsubscribe())
+            }
+
+            disconnectedCallback() {
+                // disconnected call back gets called multiple times, each time user chooses to 
+                this.shutdownHooks.forEach(fn => fn())
+            }
+
+            connectedCallback() {
+                // const shadowRoot = this.attachShadow({mode:'open'})
+                while(this.lastChild){
+                    this.removeChild(this.lastChild)
+                }
+
+                this.rootChild = document.createElement('div')
+                this.appendChild(this.rootChild)
+                this.connectNehubaHooks()
+                this.render()
+            }
+
+            render() {
+                this.rootChild.innerHTML = this.template
+                console.log(this.selectedRegion)
+                this.rootChild.querySelector('input[region]').value = this.selectedRegion ? this.selectedRegion.name : ''
+                this.rootChild.querySelector('div[editRegion]').addEventListener('click', () => {
+                    this.rootChild.querySelector('input[region]').value = ''
+                    this.selectedRegion = null
+                    this.listening = true
+                })
+            }
+        }
+
+        register('hover-region-selector-card',HoverRegionSelectorComponent)
+
+        /* reusable pill components */
+        class DismissablePill extends HTMLElement {
+            constructor() {
+                super()
+                this.name = ''
+                this.template = ``
+            }
+
+            render() {
+                this.template =
+                    `
+                <span class = "label label-default">
+                <span pillName>${this.name}</span>
+                <span class = "glyphicon glyphicon-remove" pillRemove></span>
+                </span>
+                `
+            }
+
+            connectedCallback() {
+                // const shadowRoot = this.attachShadow({mode:'open'})
+
+                while(this.lastChild){
+                    this.removeChild(this.lastChild)
+                }
+
+                this.render()
+                this.innerHTML = this.template
+                const removePill = this.querySelector('span[pillRemove]')
+                removePill.addEventListener('click', () => {
+                    this.onRemove(this.name)
+                    this.remove()
+                })
+            }
+
+            onRemove(name) { }
+        }
+        register('dismissable-pill-card', DismissablePill)
+
+        class WebJuGExGeneComponent extends HTMLElement {
+            constructor() {
+                super()
+                this.selectedGenes = []
+                this.arrDict = []
+                this.autocompleteSuggestions = []
+                this.template =
+                    `
+                <div class = "input-group">
+                <input geneInputBox type = "text" class = "form-control" placeholder = "Enter gene of interest ... ">
+                <input geneImportInput class="hidden" type="file">
+                <span class = "input-group-btn">
+                <div geneAdd class = "btn btn-default" title = "Add a gene">Add</div>
+                <div geneImport class = "btn btn-default" title = "Import a CSV file">Import</div>
+                <div geneExport class = "btn btn-default" title = "Export selected genes into a csv file">Export</div>
+                </span>
+                </div>
+                `
+            }
+
+            connectedCallback() {
+                // const shadowRoot = this.attachShadow({mode:'open'})
+                this.rootChild = document.createElement('div')
+                this.rootChild.innerHTML = this.template
+                this.appendChild(this.rootChild)
+
+                this.config()
+                this.init()
+            }
+
+            config() {
+                this.MINCHAR = 1
+            }
+
+            init() {
+                this.elGeneInputBox = this.rootChild.querySelector('input[geneInputBox]')
+                this.elGeneImportInput = this.rootChild.querySelector('input[geneImportInput]')
+                this.elGeneAdd = this.rootChild.querySelector('div[geneAdd]')
+                this.elGeneImport = this.rootChild.querySelector('div[geneImport]')
+                this.elGeneExport = this.rootChild.querySelector('div[geneExport]')
+
+                const importGeneList = (file) => {
+                    const csvReader = new FileReader()
+                    csvReader.onload = (ev) => {
+                        const csvRaw = ev.target.result
+                        this.selectedGenes.splice(0, this.selectedGenes.length)
+                        csvRaw.split(/\r|\r\n|\n|\t|\,|\;/).forEach(gene => {
+                            if (gene.length > 0)
+                                this.addGene(gene)
+                        })
+                    }
+                    csvReader.readAsText(file, 'utf-8')
+                }
+                this.elGeneImportInput.addEventListener('change', (ev) => {
+                    importGeneList(ev.target.files[0])
+                })
+                this.elGeneImport.addEventListener('click', () => {
+                    this.elGeneImportInput.click()
+                })
+                this.elGeneExport.addEventListener('click', () => {
+                    const exportGeneList = 'data:text/csv;charset=utf-8,' + this.selectedGenes.join(',')
+                    const exportGeneListURI = encodeURI(exportGeneList)
+                    const dlExportGeneList = document.createElement('a')
+                    dlExportGeneList.setAttribute('href', exportGeneListURI)
+                    document.body.appendChild(dlExportGeneList)
+                    const date = new Date()
+                    dlExportGeneList.setAttribute('download', `exported_genelist_${'' + date.getFullYear() + (date.getMonth() + 1) + date.getDate() + '_' + date.getHours() + date.getMinutes()}.csv`)
+                    dlExportGeneList.click()
+                    document.body.removeChild(dlExportGeneList)
+                })
+                this.elGeneAdd.addEventListener('click', () => {
+                    if (this.autocompleteSuggestions.length > 0 && this.elGeneInputBox.value.length >= this.MINCHAR)
+                        this.addGene(this.autocompleteSuggestions[0])
+                })
+
+                this.elGeneInputBox.addEventListener('dragenter', (ev) => {
+                    this.elGeneInputBox.setAttribute('placeholder', 'Drop file here to be uploaded')
+                })
+
+                this.elGeneInputBox.addEventListener('dragleave', (ev) => {
+                    this.elGeneInputBox.setAttribute('placeholder', 'Enter gene of interest ... ')
+                })
+
+                this.elGeneInputBox.addEventListener('drop', (ev) => {
+                    ev.preventDefault()
+                    ev.stopPropagation()
+                    ev.stopImmediatePropagation()
+                    this.elGeneInputBox.setAttribute('placeholder', 'Enter gene of interest ... ')
+                    //ev.dataTransfer.files[0]
+                })
+
+                this.elGeneInputBox.addEventListener('dragover', (ev) => {
+                    ev.preventDefault()
+                    ev.stopPropagation()
+                    ev.stopImmediatePropagation()
+                })
+
+                this.elGeneInputBox.addEventListener('keydown', (ev) => {
+                    ev.stopPropagation()
+                    ev.stopImmediatePropagation()
+                    if (ev.key == 'Enter') this.elGeneAdd.click()
+                })
+
+                this.loadExternalResources()
+                fetch(backendBasePath).then(txt => txt.json())
+                    .then(json => {
+                        this.arrDict = json
+                    })
+                    .catch(err => {
+                        console.log('failed to fetch full list of genes... using limited list of genes instead ...', e)
+                        this.arrDict = ["ADRA2A", "AVPR1B", "CHRM2", "CNR1", "CREB1", "CRH", "CRHR1", "CRHR2", "GAD2", "HTR1A", "HTR1B", "HTR1D", "HTR2A", "HTR3A", "HTR5A", "MAOA", "PDE1A", "SLC6A2", "SLC6A4", "SST", "TAC1", "TPH1", "GPR50", "CUX2", "TPH2"]
+                    })
+            }
+
+            loadExternalResources() {
+                this.autoCompleteCss = document.createElement('link')
+                this.autoCompleteCss.type = 'text/css'
+                this.autoCompleteCss.rel = 'stylesheet'
+                this.autoCompleteCss.href = basePath + 'js-autocomplete.min.css'
+
+                this.autoCompleteJs = document.createElement('script')
+                this.autoCompleteJs.onload = () => {
+                    /* append autocomplete here */
+                    this.autocompleteInput = new autoComplete({
+                        selector: this.elGeneInputBox,
+                        delay: 0,
+                        minChars: this.MINCHAR,
+                        cache: false,
+                        source: (term, suggest) => {
+                            const searchTerm = new RegExp('^' + term, 'gi')
+                            this.autocompleteSuggestions = this.arrDict.filter(dict => searchTerm.test(dict))
+                            suggest(this.autocompleteSuggestions)
+                        },
+                        onSelect: (e, term, item) => {
+                            this.addGene(term)
+                        }
+                    })
+                }
+                this.autoCompleteJs.src = basePath + 'js-autocomplete.min.js'
+
+                document.head.appendChild(this.autoCompleteJs)
+                document.head.appendChild(this.autoCompleteCss)
+            }
+
+            addGene(gene) {
+                const pill = document.createElement('dismissable-pill-card')
+                pill.onRemove = (name) =>
+                    this.selectedGenes.splice(this.selectedGenes.indexOf(name), 1)
+                pill.name = gene
+                this.rootChild.appendChild(pill)
+                this.selectedGenes.push(gene)
+                this.elGeneInputBox.value = ''
+                this.elGeneInputBox.blur()
+                this.elGeneInputBox.focus()
+            }
+        }
+
+        register('fzj-xg-webjugex-gene-card', WebJuGExGeneComponent)
+
+        class WebJuGExSearchComponent extends HTMLElement {
+            constructor() {
+                super()
+                this.template = `
+                <div>
+                    <div class = "col-md-12">
+                    <small>
+                        Find a set of differentially expressed genes between two user defined volumes of interest based on JuBrain maps.
+                        The tool downloads expression values of user specified sets of genes from Allen Brain API.
+                        Then, it uses zscores to find which genes are expressed differentially between the user specified regions of interests.
+                        After the analysis is finished, the genes and their calculated p values are displayed. There is also an option of downloading the gene names and their p values
+                        and the roi coordinates used in the analysis.
+                        Please select two regions of interest, and at least one gene :
+                    </small>
+                </div>
+                <div class = "col-md-12">
+                    <hover-region-selector-card area1>
+                    </hover-region-selector-card>
+                </div>
+                <div class = "col-md-12">
+                    <hover-region-selector-card area2>
+                    </hover-region-selector-card>
+                </div>
+                <div class = "col-md-12">
+                    <div class = "input-group">
+                        <span class = "input-group-addon">
+                            Threshold
+                        </span>
+                        <input value = "0.20" class = "form-control" type = "range" min = "0" max = "1" step = "0.01" threshold \>
+                        <span class = "input-group-addon" thresholdValue>
+                            0.20
+                        </span>
+                    </div>
+                </div>
+                <div class = "col-md-12">
+                    <div class="input-group">
+                        <input id = "fzj-hb-jugex-singleprobe" name = "fzj-hb-jugex-singleprobe" type="checkbox" probemode> 
+                        <label for = "fzj-hb-jugex-singleprobe">Single Probe Mode</label>
+                    </div>
+                </div>
+                <div class = "col-md-12">
+                    <div class = "input-group">
+                        <input name = "fzj-hb-jugex-hemisphere" type = "radio" id = "fzj-hb-jugex-hemisphere-lh" value = "left-hemisphere" checked/>
+                        <label for = "fzj-hb-jugex-hemisphere-lh">Left Hemisphere</label>
+                    </div>
+                    <div class = "input-group">
+                        <input name = "fzj-hb-jugex-hemisphere" type = "radio" id = "fzj-hb-jugex-hemisphere-rh" value = "right-hemisphere"/>
+                        <label for = "fzj-hb-jugex-hemisphere-rh">Right Hemisphere</label>
+                    </div>
+                </div>
+                <div>
+                    <div class = "col-md-12">
+                        <fzj-xg-webjugex-gene-card>
+                        </fzj-xg-webjugex-gene-card>
+                    </div>
+                </div>
+                <div>
+                    <div class = "col-md-12">
+                        <div class = "btn btn-default btn-block" analysisSubmit>
+                            Start differential analysis
+                        </div>
+                    </div>
+                </div>
+                    `
+                this.mouseEventSubscription = this.rootChild = this.threshold = this.elArea1 = this.elArea2 = null
+                this.selectedGenes = []
+
+                this.datasets = []
+
+                this.datasetSub = window.interactiveViewer.metadata.datasetsBSubject.subscribe(datasets=>{
+                    this.datasets = datasets
+                })
+
+                
+        // const createsth = ()=>{
+        //     const div1 = document.createElement('div')
+        //     const child1 = document.createElement('div')
+        //     const child2 = document.createElement('div')
+
+        //     child1.innerHTML = 'helo'
+        //     child2.innerHTML = 'world'
+        //     div1.appendChild(child1)
+        //     div1.appendChild(child2)
+
+        //     return [div1,child1,child2]
+        // }
+        // const container = document.getElementById('fzj.xg.webjugex.container')
+
+        // const div2 = createsth()
+        // container.appendChild(div2[0])
+
+            }
+
+            connectedCallback() {
+                
+                while(this.lastChild){
+                    this.removeChild(this.lastChild)
+                }
+
+                // const shadowRoot = this.attachShadow({mode:'open'})
+                this.rootChild = document.createElement('div')
+                this.rootChild.innerHTML = this.template
+                this.appendChild(this.rootChild)
+
+                /* init */
+                this.init()
+
+                /* attach click listeners */
+                this.onViewerClick()
+
+            }
+
+            init() {
+                this.elArea1 = this.rootChild.querySelector('hover-region-selector-card[area1]')
+                this.elArea2 = this.rootChild.querySelector('hover-region-selector-card[area2]')
+                this.elArea1.listening = true
+                this.elArea2.listening = false
+                this.probemodeval = false
+
+                this.elGenesInput = this.rootChild.querySelector('fzj-xg-webjugex-gene-card')
+
+                this.elAnalysisSubmit = this.rootChild.querySelector('div[analysisSubmit]')
+                this.elAnalysisSubmit.style.marginBottom = '20px'
+                this.elAnalysisSubmit.addEventListener('click', () => {
+                    this.analysisGo()
+                })
+
+                this.elThreshold = this.rootChild.querySelector('input[threshold]')
+                const elThresholdValue = this.rootChild.querySelector('span[thresholdValue]')
+                this.elThreshold.addEventListener('input', (ev) => {
+                    elThresholdValue.innerHTML = parseFloat(this.elThreshold.value).toFixed(2)
+                })
+            }
+
+            onViewerClick() {
+                this.mouseEventSubscription = window.interactiveViewer.viewerHandle.mouseEvent
+                    .subscribe(ev => {
+                        if(ev.eventName !== 'click') return
+                        if (this.elArea1.listening && this.elArea2.listening) {
+                            this.elArea1.listening = false
+                        }
+                        else if (this.elArea2.listening) {
+                            this.elArea2.listening = false
+                        }
+                        else if (this.elArea1.listening) {
+                            if (this.elArea2.selectedRegion == null) {
+                                this.elArea1.listening = false
+                                this.elArea2.listening = true
+                            }
+                            else if (this.elArea2.selectedRegion != null) {
+                                this.elArea1.listening = false
+                            }
+                        }
+                    })
+
+            }
+
+            analysisGo() {
+                /* test for submit conditions */
+                const hemisphere = this.rootChild.querySelector('input[name="fzj-hb-jugex-hemisphere"]:checked').value
+
+                if (this.elArea1.selectedRegion == null || this.elArea2.selectedRegion == null || this.elGenesInput.selectedGenes.length < 1) {
+                    const resultCard = document.createElement('fzj-xg-webjugex-result-failure-card')
+                    
+                    const container = document.getElementById('fzj.xg.webjugex.container')
+
+                    container.appendChild(resultCard)
+                    let e = 'Error: We need '
+                    if (this.elArea1.selectedRegion == null || this.elArea2.selectedRegion == null) e += 'both areas to be defined and '
+                    if (this.elGenesInput.selectedGenes.length < 1) e += 'atleast one gene'
+                    else e = e.substr(0, 40)
+                    e += '.'
+                    resultCard.panelBody.innerHTML = e
+                    return
+                }
+
+                
+                console.log(this.elArea1.selectedRegion.name,
+                    this.elArea2.selectedRegion.name,
+                    this.elArea1.selectedRegion.PMapURL,
+                    this.elArea2.selectedRegion.PMapURL,
+                    this.elThreshold.value,
+                    this.elGenesInput.selectedGenes,
+                    hemisphere)
+
+                const getPmap = (name) => {
+                    if(name == 'AStr (Amygdala)') throw Error('AStr (Amygdala) has not yet been implemented in MNI152')
+                    
+                    
+
+                    const dataset = this.datasets.find(dataset => dataset.type === 'Cytoarchitectonic Probabilistic Map' && dataset.regionName[0].regionName === name)
+                    const url = dataset.files[0].url
+                    console.log('getpmap', url)
+                    const host = 'https://neuroglancer-dev.humanbrainproject.org'
+                    // const host = 'http://offline-neuroglancer:80'
+                    const mni152url = `${host}/precomputed/JuBrain/v2.2c/PMaps/MNI152/${url.substring(url.lastIndexOf('/') + 1).replace('.nii',hemisphere == 'left-hemisphere' ? '_l.nii' : '_r.nii')}`
+                    console.log('MNI152 PMap',mni152url)
+                    if (dataset) { return mni152url } else { throw new Error('could not find PMap') }
+                }
+
+                const newArea1 = {
+                    name: this.elArea1.selectedRegion.name,
+                    PMapURL: getPmap(this.elArea1.selectedRegion.name)
+                }
+                const newArea2 = {
+                    name: this.elArea2.selectedRegion.name,
+                    PMapURL: getPmap(this.elArea2.selectedRegion.name)
+                }
+                console.log('fixed loop reference',
+                    newArea1.name,
+                    newArea2.name,
+                    newArea1.PMapURL,
+                    newArea2.PMapURL,
+                    this.elThreshold.value,
+                    this.elGenesInput.selectedGenes)
+
+                this.sendAnalysis({
+                    area1: newArea1,
+                    area2: newArea2,
+                    threshold: this.elThreshold.value,
+                    selectedGenes: this.elGenesInput.selectedGenes,
+                    mode: this.rootChild.querySelector('input[probemode]').checked
+                })
+            }
+
+            sendAnalysis(analysisInfo) {
+
+                const analysisCard = document.createElement('fzj-xg-webjugex-analysis-card')
+                analysisCard.analysisObj = analysisInfo
+                
+                const container = document.getElementById('fzj.xg.webjugex.container')
+
+                container.appendChild(analysisCard)
+                const headers = new Headers()
+                headers.append('Content-Type', 'application/json')
+                const request = new Request(backendBasePath + 'jugex', {
+                    method: 'POST',
+                    headers: headers,
+                    mode: 'cors',
+                    body: JSON.stringify(analysisInfo)
+                })
+                fetch(request)
+                    .then(resp => {
+                        if (resp.ok) {
+                            return Promise.resolve(resp)
+                        }
+                        else {
+                            return new Promise((resolve, reject) => {
+                                resp.text()
+                                    .then(text => reject(text))
+                            })
+                        }
+                    })
+                    .then(resp => resp.text())
+                    .then(text => {
+
+                        const createRow = () => {
+                            const domDownload = document.createElement('div')
+                            domDownload.style.display = 'flex'
+                            domDownload.style.flexDirection = 'row'
+                            const col1 = document.createElement('div')
+                            const col2 = document.createElement('div')
+                            col2.style.flex = col1.style.flex = '0 0 50%'
+                            domDownload.appendChild(col1)
+                            domDownload.appendChild(col2)
+                            return [domDownload, col1, col2]
+                        }
+
+                        debugger
+                        
+                        container.removeChild(analysisCard)
+                        const resultCard = document.createElement('fzj-xg-webjugex-result-success-card')
+                        container.appendChild(resultCard)
+                        const date = new Date()
+                        const dateDownload = '' + date.getFullYear() + (date.getMonth() + 1) + date.getDate() + '_' + date.getHours() + ':' + date.getMinutes()
+                        resultCard.panelHeader.innerHTML += '(' + dateDownload + ')'
+                        resultCard.resultObj = JSON.parse(text)
+                        const extension = createRow()
+                        extension[0].style.order = -1
+                        if (resultCard.resultObj.length == 3) {
+                            extension[1].innerHTML = 'Probe ids'
+                        }
+                        else if (resultCard.resultObj.length == 2) {
+                            extension[1].innerHTML = 'Gene Symbol'
+                        }
+                        extension[1].style.fontWeight = 900
+                        extension[2].innerHTML = 'Pval'
+                        extension[2].style.fontWeight = 900
+                        resultCard.panelBody.style.maxHeight = '400px'
+                        resultCard.panelBody.style.overflowY = 'scroll'
+                        resultCard.panelBody.appendChild(extension[0])
+                        let count = 0
+                        for (let key in resultCard.resultObj[1]) {
+                            count = count + 1
+                        }
+                        for (let key in resultCard.resultObj[1]) {
+                            resultCard.pvalString += [key, resultCard.resultObj[1][key]].join(',') + '\n'
+                        }
+                        if (count < 2) {
+                            for (let key in resultCard.resultObj[1]) {
+                                const extension1 = createRow()
+                                extension1[0].style.order = Number(resultCard.resultObj[1][key]) ? Math.round(Number(resultCard.resultObj[1][key]) * 1000) : 1000
+                                extension1[1].innerHTML = key
+                                extension1[2].innerHTML = resultCard.resultObj[1][key]
+                                resultCard.panelBody.appendChild(extension[0])
+                            }
+                        }
+                        else {
+                            let v = 0
+                            for (let key in resultCard.resultObj[1]) {
+                                const extension2 = createRow()
+                                extension2[0].style.order = Number(resultCard.resultObj[1][key]) ? Math.round(Number(resultCard.resultObj[1][key]) * 1000) : 1000
+                                if (v == 0 || v == count - 1) {
+                                    extension2[1].innerHTML = key
+                                    extension2[2].innerHTML = resultCard.resultObj[1][key]
+                                }
+                                else if (v == 1 || v == 2) {
+                                    extension2[1].innerHTML = '...'
+                                    extension2[2].innerHTML = '...'
+                                }
+                                v = v + 1
+                                resultCard.panelBody.appendChild(extension2[0])
+                            }
+                        }
+                        resultCard.areaString = 'ROI, x, y, z, '
+                        if (resultCard.resultObj.length == 3) {
+                            resultCard.areaString += resultCard.resultObj[2] + '\n'
+                        }
+                        else {
+                            for (let key in resultCard.resultObj[1]) {
+                                resultCard.areaString += key + ','
+                            }
+                            resultCard.areaString = resultCard.areaString.slice(0, -1)
+                            resultCard.areaString += '\n'
+                        }
+    
+                        /* injected to add landmarks */
+                        const newLandmarks = []
+    
+                        for (let key in resultCard.resultObj[0]) {
+                            for (let i in resultCard.resultObj[0][key]) {
+                                resultCard.areaString += key + ',' + resultCard.resultObj[0][key][i]['xyz'].join(',') + ',' + resultCard.resultObj[0][key][i]['winsorzed_mean'] + '\n'
+                                
+                                const pos = resultCard.resultObj[0][key][i]['xyz']
+                                const newLandmark = {
+                                    pos : pos,
+                                    id : pos.join('_'),
+                                    properties : pos.join('_'),
+                                    hover:false
+                                }
+                                newLandmarks.push(newLandmark)
+                                // landmarkService.addLandmark(newLandmark)
+                            }
+                        }
+    
+                        // newLandmarks.forEach((lm,idx)=>landmarkService.TEMP_parseLandmarkToVtk(lm,idx))
+    
+                        /* end */
+    
+                        const domDownloadPVal = parseContentToCsv(resultCard.pvalString)
+                        domDownloadPVal.innerHTML = 'Download Pvals of genes (' + dateDownload + ')'
+                        domDownloadPVal.setAttribute('download', 'PVal.csv')
+                        resultCard.panelBody.append(domDownloadPVal)
+                        const linebreak = document.createElement("br")
+                        resultCard.panelBody.append(linebreak)
+                        const domDownloadArea = parseContentToCsv(resultCard.areaString)
+                        domDownloadArea.innerHTML = 'Download sample coordinates (' + dateDownload + ')'
+                        domDownloadArea.setAttribute('download', `SampleCoordinates.csv`)
+                        domDownloadArea.style.order = -3
+                        resultCard.panelBody.append(domDownloadArea)
+                    })
+                    .catch(e => {
+                        console.log('Here 2')
+                        container.removeChild(analysisCard)
+                        const resultCard = document.createElement('fzj-xg-webjugex-result-failure-card')
+                        container.appendChild(resultCard)
+                        console.log('error', e)
+                        resultCard.panelBody.innerHTML = e
+                    })
+    
+            };
+        }
+
+        register('fzj-xg-webjugex-search-card', WebJuGExSearchComponent)
+
+        /* custom class for analysis-card */
+        class WebJuGExAnalysisComponent extends HTMLElement {
+            constructor() {
+                super()
+                this.template = ``
+                this.analysisObj = {}
+                this.status = 'pending'
+            }
+
+            connectedCallback() {
+                
+                while(this.lastChild){
+                    this.removeChild(this.lastChild)
+                }
+
+                // const shadowRoot = this.attachShadow({mode:'open'})
+                this.childRoot = document.createElement('div')
+                this.appendChild(this.childRoot)
+                this.render()
+                this.panelHeader = this.childRoot.querySelector('[panelHeader]')
+            }
+
+            render() {
+
+                this.template =
+                    `
+                <div>
+                <div class="progress">
+                <div class="progress-bar progress-bar-striped active" style="width:100%"></div>
+                </div>
+                </div>
+                `
+                this.childRoot.innerHTML = this.template
+            }
+        }
+        
+        register('fzj-xg-webjugex-analysis-card', WebJuGExAnalysisComponent)
+
+        const parseContentToCsv = (content) => {
+            const CSVContent = 'data:text/csv;charset=utf-8,' + content
+            const CSVURI = encodeURI(CSVContent)
+            const domDownload = document.createElement('a')
+            domDownload.setAttribute('href', CSVURI)
+            return domDownload
+        }
+        /* custom class for analysis-card */
+
+
+        class WebJuGExResultSuccessComponent extends HTMLElement {
+            constructor() {
+                super()
+                this.template = ``
+                this.resultObj = {}
+                this.pvalString = ''
+                this.areaString = ''
+                this.status = 'pending'
+            }
+
+            connectedCallback() {
+                
+                while(this.lastChild){
+                    this.removeChild(this.lastChild)
+                }
+
+                // const shadowRoot = this.attachShadow({mode:'open'})
+                this.childRoot = document.createElement('div')
+                this.appendChild(this.childRoot)
+                this.render()
+
+                this.panelHeader = this.childRoot.querySelector('[panelHeader]')
+                this.panelBody = this.childRoot.querySelector('[panelBody]')
+                this.panelHeader.addEventListener('click', () => {
+                    this.uiTogglePanelBody()
+                })
+            }
+
+            uiTogglePanelBody() {
+                if (/hidden/.test(this.panelBody.className)) {
+                    this.panelBody.classList.remove('hidden')
+                } else {
+                    this.panelBody.classList.add('hidden')
+                }
+            }
+
+            render() {
+                this.template =
+                    `
+                <div>
+                <div class = "panel panel-success">
+                <div class = "btn btn-default btn-block panel-heading" panelHeader>
+                <span class="glyphicon glyphicon-ok"></span> Request completed! <u> Details below.</u>
+                </div>
+                <div class = "panel-body hidden" panelBody>
+                </div>
+                <div class = "panel-footer hidden" panelFooter>
+                </div>
+                </div>
+                </div>
+                `
+                this.childRoot.innerHTML = this.template
+            }
+        }
+
+        class WebJuGExResultFailureComponent extends HTMLElement {
+            constructor() {
+                super()
+                this.template = ``
+                this.resultObj = {}
+                this.pvalString = ''
+                this.areaString = ''
+                this.status = 'pending'
+            }
+
+            connectedCallback() {
+                
+                while(this.lastChild){
+                    this.removeChild(this.lastChild)
+                }
+
+                // const shadowRoot = this.attachShadow({mode:'open'})
+                this.childRoot = document.createElement('div')
+                this.appendChild(this.childRoot)
+                this.render()
+
+                this.panelHeader = this.childRoot.querySelector('[panelHeader]')
+                this.panelBody = this.childRoot.querySelector('[panelBody]')
+                this.panelHeader.addEventListener('click', () => {
+                    this.uiTogglePanelBody()
+                })
+            }
+
+            uiTogglePanelBody() {
+                if (/hidden/.test(this.panelBody.className)) {
+                    this.panelBody.classList.remove('hidden')
+                } else {
+                    this.panelBody.classList.add('hidden')
+                }
+            }
+
+            render() {
+                this.template =
+                    `
+                <div>
+                <div class = "panel panel-danger">
+                <div class = "btn btn-default btn-block panel-heading" panelHeader>
+                <span class="glyphicon glyphicon-remove"></span> Error. Check below.
+                </div>
+                <div class = "panel-body hidden" panelBody>
+                </div>
+                <div class = "panel-footer hidden" panelFooter>
+                </div>
+                </div>
+                </div>
+                `
+                this.childRoot.innerHTML = this.template
+            }
+        }
+
+        register('fzj-xg-webjugex-result-success-card', WebJuGExResultSuccessComponent)
+        register('fzj-xg-webjugex-result-failure-card', WebJuGExResultFailureComponent)
+
+        interactiveViewer.pluginControl['fzj.hb.jugex'].onShutdown(() => {
+            console.log('shutting down fzj jugex')
+            // landmarkService.TEMP_clearVtkLayers()
+            interactiveViewer.pluginControl.unloadExternalLibraries(['webcomponentsLite'])
+        })
+
+    }
+    interactiveViewer.pluginControl.loadExternalLibraries(['webcomponentsLite'])
+        .then(() => code())
+        .catch(console.warn)
+})()
+
+
diff --git a/src/plugin_examples/jugex/template.html b/src/plugin_examples/jugex/template.html
new file mode 100644
index 0000000000000000000000000000000000000000..5ab759f6278401a09cf337d7630b076175f92a68
--- /dev/null
+++ b/src/plugin_examples/jugex/template.html
@@ -0,0 +1,10 @@
+<div id = "fzj.xg.webjugex.container">
+  <fzj-xg-webjugex-search-card>
+  </fzj-xg-webjugex-search-card>
+</div>
+<style>
+fzj-xg-webjugex-search-card
+{
+  display:inline-block;
+}
+</style>
\ No newline at end of file
diff --git a/src/plugin_examples/migrationGuide.md b/src/plugin_examples/migrationGuide.md
new file mode 100644
index 0000000000000000000000000000000000000000..1489d5b848ab70382b63575190309020b0f8c148
--- /dev/null
+++ b/src/plugin_examples/migrationGuide.md
@@ -0,0 +1,64 @@
+Plugin Migration Guide (v0.1.0 => v0.2.0)
+======
+Plugin APIs have changed drastically from v0.1.0 to v0.2.0. Here is a list of plugin API from v0.1.0, and how it has changed moving to v0.2.0.
+
+**n.b.** `webcomponents-lite.js` is no longer included by default. You will need to request it explicitly with `window.interactiveViewer.pluginControl.loadExternalLibraries()` and unload it once you are done.
+
+---
+
+- ~~*window.nehubaUI*~~ removed
+  - ~~*metadata*~~ => **window.interactiveViewer.metadata**
+    - ~~*selectedTemplate* : nullable Object~~ removed. use **window.interactiveViewer.metadata.selectedTemplateBSubject** instead
+    - ~~*availableTemplates* : Array of TemplateDescriptors (empty array if no templates are available)~~ => **window.interactiveViewer.metadata.loadedTemplates**
+    - ~~*selectedParcellation* : nullable Object~~ removed. use **window.interactiveViewer.metadata.selectedParcellationBSubject** instead
+    - ~~*selectedRegions* : Array of Object (empty array if no regions are selected)~~ removed. use **window.interactiveViewer.metadata.selectedRegionsBSubject** instead
+
+- ~~window.pluginControl['YOURPLUGINNAME'] *nb: may be undefined if yourpluginname is incorrect*~~ => **window.interactiveViewer.pluginControl[YOURPLUGINNAME]**
+  - blink(sec?:number) : Function that causes the floating widget to blink, attempt to grab user attention
+  - ~~pushMessage(message:string) : Function that pushes a message that are displayed as a popover if the widget is minimised. No effect if the widget is not miniminised.~~ removed
+  - shutdown() : Function that causes the widget to shutdown dynamically. (triggers onShutdown callback)
+  - onShutdown(callback) : Attaches a callback function, which is called when the plugin is shutdown.
+  
+- ~~*window.viewerHandle*~~ => **window.interactiveViewer.viewerHandle**
+  - ~~*loadTemplate(TemplateDescriptor)* : Function that loads a new template~~ removed. use **window.interactiveViewer.metadata.selectedTemplateBSubject** instead
+  - ~~*onViewerInit(callback)* : Functional that allows a callback function to be called just before a nehuba viewer is initialised~~ removed
+  - ~~*afterViewerInit(callback)* : Function that allows a callback function to be called just after a nehuba viewer is initialised~~ removed
+  - ~~*onViewerDestroy(callback)* : Function that allows a callback function be called just before a nehuba viewer is destroyed~~ removed
+  - ~~*onParcellationLoading(callback)* : Function that allows a callback function to be called just before a parcellation is selected~~ removed
+  - ~~*afterParcellationLoading(callback)* : Function that allows a callback function to be called just after a parcellation is selected~~ removed
+  - *setNavigationLoc(loc,realSpace?)* : Function that teleports to loc : number[3]. Optional argument to determine if the loc is in realspace (default) or voxelspace.
+  - ~~*setNavigationOrientation(ori)* : Function that teleports to ori : number[4]. (Does not work currently)~~ => **setNavigationOri(ori)** (still non-functional)
+  - *moveToNavigationLoc(loc,realSpace?)* : same as *setNavigationLoc(loc,realSpace?)*, except moves to target location over 500ms.
+  - *showSegment(id)* : Function that selectes a segment in the viewer and UI. 
+  - *hideSegment(id)* : Function that deselects a segment in the viewer and UI.
+  - *showAllSegments()* : Function that selects all segments.
+  - *hideAllSegments()* : Function that deselects all segments.
+  - *loadLayer(layerObject)* : Function that loads a custom neuroglancer compatible layer into the viewer (e.g. precomputed, NIFTI, etc). Does not influence UI. 
+  - ~~*reapplyNehubaMeshFix()* Function that reapplies the cosmetic change to NehubaViewer (such as custom colour map, if defined)~~ removed. use **applyColourMap(colourMap)** instead
+  - *mouseEvent* RxJs Observable. Read more at [rxjs doc](http://reactivex.io/rxjs/)
+    - *mouseEvent.filter(filterFn:({eventName : String, event: Event})=>boolean)* returns an Observable. Filters the event stream according to the filter function.
+    - *mouseEvent.map(mapFn:({eventName : String, event: Event})=>any)* returns an Observable. Map the event stream according to the map function.
+    - *mouseEvent.subscribe(callback:({eventName : String , event : Event})=>void)* returns an Subscriber instance. Call *Subscriber.unsubscribe()* when done to avoid memory leak. 
+  - *mouseOverNehuba* RxJs Observable. Read more at [rxjs doc](http://reactivex.io/rxjs)
+    - *mouseOverNehuba.filter* && *mouseOvernehuba.map* see above
+    - *mouseOverNehuba.subscribe(callback:({nehubaOutput : any, foundRegion : any})=>void)*
+
+- ~~*window.uiHandle*~~ => **window.interactiveViewer.uiHandle**
+  - ~~*onTemplateSelection(callback)* : Function that allows a callback function to be called just after user clicks to navigate to a new template, before *selectedTemplate* is updated~~ removed. use **window.interactiveViewer.metadata.selectedTemplateBSubject** instead
+  - ~~*afterTemplateSelection(callback)* : Function that allows a callback function to be called after the template selection process is complete, and *selectedTemplate* is updated~~ removed
+  - ~~*onParcellationSelection(callback)* : Function that attach a callback function to user selecting a different parcellation~~ removed. use **window.interactiveViewer.metadata.selectedParcellationBSubject** instead.
+  - ~~*afterParcellationSelection(callback)* : Function that attach a callback function to be called after the parcellation selection process is complete and *selectedParcellation* is updated.~~ removed
+  - *modalControl*
+    - *getModalHandler()* : Function returning a handler to change/show/hide/listen to a Modal. 
+    - *modalHander* methods:
+      - *hide()* : Dynamically hides the modal
+      - *show()* : Shows the modal
+      - *onHide(callback(reason)=>void)* : Attaches an onHide callback. 
+      - *onHidden(callback(reason)=>void)* : Attaches an onHidden callback. 
+      - *onShow(callback(reason)=>void)* : Attaches an onShow callback. 
+      - *onShown(callback(reason)=>void)* : Attaches an onShown callback.
+    - *modalHandler* properties:
+      - title : title of the modal (String)
+      - body : body of the modal shown (JSON, Array, String)
+      - footer : footer of the modal (String)
+      - config : config of the modal
\ No newline at end of file
diff --git a/src/plugin_examples/plugin_README.md b/src/plugin_examples/plugin_README.md
new file mode 100644
index 0000000000000000000000000000000000000000..6a76357bf8122f52de712b0f5857327bcc3b8f45
--- /dev/null
+++ b/src/plugin_examples/plugin_README.md
@@ -0,0 +1,92 @@
+Plugin README
+======
+A plugin needs to contain three files. 
+- Manifest JSON
+- template HTML
+- script JS. 
+
+These files need to be served by GET requests over HTTP with appropriate CORS header. If your application requires a backend, it is strongly recommended to host these three files with your backend. 
+
+---
+Manifest JSON
+------
+The manifest JSON file describes the metadata associated with the plugin. 
+
+```json
+{
+  "name":"fzj.xg.helloWorld",
+  "templateURL":"http://LINK-TO-YOUR-PLUGIN-TEMPLATE/template.html",
+  "scriptURL":"http://LINK-TO-YOUR-PLUGIN-SCRIPT/script.js"
+}
+```
+*NB* 
+- Plugin name must be unique globally. To prevent plugin name clashing, please adhere to the convention of naming your package **AFFILIATION.AUTHORNAME.PACKAGENAME**. 
+
+
+---
+Template HTML
+------
+The template HTML file describes the HTML view that will be rendered in the widget.
+
+
+```html
+<form>
+  <div class = "input-group">
+    <span class = "input-group-addon">Area 1</span>
+    <input type = "text" id = "fzj.xg.helloWorld.area1" name = "fzj.xg.helloWorld.area1" class = "form-control" placeholder="Select a region" value = "">
+  </div>
+
+  <div class = "input-group">
+    <span class = "input-group-addon">Area 2</span>
+    <input type = "text" id = "fzj.xg.helloWorld.area2" name = "fzj.xg.helloWorld.area2" class = "form-control" placeholder="Select a region" value = "">
+  </div>
+
+  <hr class = "col-md-10">
+
+  <div class = "col-md-12">
+    Select genes of interest:
+  </div>
+  <div class = "input-group">
+    <input type = "text" id = "fzj.xg.helloWorld.genes" name = "fzj.xg.helloWorld.genes" class = "form-control" placeholder = "Genes of interest ...">
+    <span class = "input-group-btn">
+      <button id = "fzj.xg.helloWorld.addgenes" name = "fzj.xg.helloWorld.addgenes" class = "btn btn-default" type = "button">Add</button>
+    </span>
+  </div>
+
+  <hr class = "col-md-10">
+
+  <button id = "fzj.xg.helloWorld.submit" name = "fzj.xg.helloWorld.submit" type = "button" class = "btn btn-default btn-block">Submit</button>
+
+  <hr class = "col-md-10">
+
+  <div class = "col-md-12" id = "fzj.xg.helloWorld.result">
+
+  </div>
+</form>
+```
+*NB*
+- *bootstrap 3.6* css is already included for templating.
+- keep in mind of the widget width restriction (400px) when crafting the template
+- whilst there are no vertical limits on the widget, contents can be rendered outside the viewport. Consider setting the *max-height* attribute.
+- your template and script will interact with each other likely via *element id*. As a result, it is highly recommended that unique id's are used. Please adhere to the convention: **AFFILIATION.AUTHOR.PACKAGENAME.ELEMENTID** 
+---
+Script JS
+------
+The script will always be appended **after** the rendering of the template. 
+
+```javascript
+(()=>{
+  /* your code here */
+  const submitButton = document.getElemenById('fzj.xg.helloWorld.submit')
+  submitButton.addEventListener('click',(ev)=>{
+    console.log('submit button was clicked')
+  })
+})()
+```
+*NB*
+- ensure the script is scoped locally, instead of poisoning the global scope
+- for every observable subscription, call *unsubscribe()* in the *onShutdown* callback
+- some frameworks such as *jquery2*, *jquery3*, *react/reactdom* and *webcomponents* can be loaded via *interactiveViewer.pluinControl.loadExternalLibraries([LIBRARY_NAME_1, LIBRARY_NAME_2])*. if the libraries are loaded, remember to hook *interactiveViewer.pluginControl.unloadExternalLibraries([LIBRARY_NAME_1,LIBRARY_NAME_2])* in the *onShutdown* callback
+- when/if using webcomponents, please be aware that the `connectedCallback()` and `disconnectedCallback()` will be called everytime user toggle between *floating* and *docked* modes. 
+- when user navigate to a new template all existing widgets will be destroyed.
+- for a list of APIs, see [plugin_api.md](plugin_api.md)
diff --git a/src/plugin_examples/plugin_api.md b/src/plugin_examples/plugin_api.md
new file mode 100644
index 0000000000000000000000000000000000000000..1ea94a6449132d111c69fe4b337732c0c2becf44
--- /dev/null
+++ b/src/plugin_examples/plugin_api.md
@@ -0,0 +1,171 @@
+Plugin APIs
+======
+
+[plugin migration guide](migrationGuide.md)
+
+window.interactiveViewer
+---
+- metadata
+
+  - *selectedTemplateBSubject* : BehaviourSubject that emits a TemplateDescriptor object whenever a template is selected. Emits null onInit.
+
+  - *selectedParcellationBSubject* : BehaviourSubject that emits a ParcellationDescriptor object whenever a parcellation is selected. n.b. selecting a new template automatically select the first available parcellation. Emits null onInit.
+
+  - *selectedRegionsBSubject* BehaviourSubject that emits an Array of RegionDescriptor objects whenever the list of selected regions changes. Emits empty array onInit.
+
+  - *loadedTemplates* : Array of TemplateDescriptor objects. Loaded asynchronously onInit.
+
+  - *regionsLabelIndexMap* Map of labelIndex (used by neuroglancer and nehuba) to the corresponding RegionDescriptor object.
+
+- viewerHandle
+
+  - *setNavigationLoc(coordinates,realspace?:boolean)* Function that teleports the navigation state to coordinates : [x:number,y:number,z:number]. Optional arg determine if the set of coordinates is in realspace (default) or voxelspace.
+
+  - *moveToNavigationLoc(coordinates,realspace?:boolean)*
+  same as *setNavigationLoc(coordinates,realspace?)*, except the action is carried out over 500ms.
+
+  - *setNavigationOri(ori)* (not yet live) Function that sets the orientation state of the viewer.
+
+  - *moveToNavigationOri(ori)* (not yet live) same as *setNavigationOri*, except the action is carried out over 500ms.
+
+  - *showSegment(labelIndex)* Function that shows a specific segment. Will trigger *selectedRegionsBSubject*.
+
+  - *hideSegment(labelIndex)* Function that hides a specific segment. Will trigger *selectRegionsBSubject*
+  
+  - *showAllSegments()* Function that shows all segments. Will trigger *selectRegionsBSubject*
+
+  - *hideAllSegments()* Function that hides all segments. Will trigger *selectRegionBSubject*
+
+  - *segmentColourMap* : Map of *labelIndex* to an object with the shape of `{red: number, green: number, blue: number}`.
+
+  - *applyColourMap(colourMap)* Function that applies a custom colour map (Map of number to and object with the shape of `{red: number , green: number , blue: number}`)
+
+  - *loadLayer(layerObject)* Function that loads *ManagedLayersWithSpecification* directly to neuroglancer. Returns the values of the object successfully added. **n.b.** advanced feature, will likely break other functionalities. **n.b.** if the layer name is already taken, the layer will not be added.
+  
+  ```javascript
+  const obj = {
+    'advanced layer' : {
+      type : 'image',
+      source : 'nifti://http://example.com/data/nifti.nii',
+    },
+    'advanced layer 2' : {
+      type : 'mesh',
+      source : 'vtk://http://example.com/data/vtk.vtk'
+    }
+  }
+  const returnValue = window.interactiveViewer.viewerHandle.loadLayer(obj)
+  /* loads two layers, an image nifti layer and a mesh vtk layer */
+
+  console.log(returnValue)
+  /* prints
+  
+  [{ 
+    type : 'image', 
+    source : 'nifti...' 
+  },
+  {
+    type : 'mesh',
+    source : 'vtk...'
+  }] 
+  */
+  ```
+
+  - *removeLayer(layerObject)* Function that removes *ManagedLayersWithSpecification*, returns an array of the names of the layers removed. **n.b.** advanced feature. may break other functionalities.
+  ```js
+  const obj = {
+    'name' : /^PMap/
+  }
+  const returnValue = window.interactiveViewer.viewerHandle.removeLayer(obj)
+  
+  console.log(returnValue)
+  /* prints
+  ['PMap 001','PMap 002']
+  */
+  ```
+  - *setLayerVisibility(layerObject,visible)* Function that sets the visibility of a layer. Returns the names of all the layers that are affected as an Array of string.
+
+  ```js
+  const obj = {
+    'type' : 'segmentation'
+  }
+
+  window.interactiveViewer.viewerHandle.setLayerVisibility(obj,false)
+
+  /* turns off all the segmentation layers */
+  ```
+
+  - *mouseEvent* Subject that emits an object shaped `{ eventName : string, event: event }` when a user triggers a mouse event on the viewer. 
+
+  - *mouseOverNehuba* BehaviourSubject that emits an object shaped `{ nehubaOutput : number | null, foundRegion : RegionDescriptor | null }`
+
+- uiHandle
+
+  - modalControl
+
+    - *getModalHandler()* returns a modalHandlerObject
+
+    - *modalHandler*
+
+      - *hide()* : Dynamically hides the modal
+      - *show()* : Shows the modal
+      - *onHide(callback(reason)=>void)* : Attaches an onHide callback. 
+      - *onHidden(callback(reason)=>void)* : Attaches an onHidden callback. 
+      - *onShow(callback(reason)=>void)* : Attaches an onShow callback. 
+      - *onShown(callback(reason)=>void)* : Attaches an onShown callback.
+      - title : title of the modal (String)
+      - body : body of the modal shown (JSON, Array, String)
+      - footer : footer of the modal (String)
+      - config : config of the modal
+
+  - *viewingModeBSubject* BehaviourSubject emitting strings when viewing mode has changed. 
+
+- pluginControl
+
+  - *loadExternalLibraries([LIBRARY_NAME_1,LIBRARY_NAME_2])* Function that loads external libraries. Pass the name of the libraries as an Array of string, and returns a Promise. When promise resolves, the libraries are loaded. **n.b.** while unlikely, there is a possibility that multiple requests to load external libraries in quick succession can cause the promise to resolve before the library is actually loaded. 
+
+  ```js
+  const currentlySupportedLibraries = ['jquery2','jquery3','webcomponentsLite','react16','reactdom16']
+
+  window.interactivewViewer.loadExternalLibraries(currentlySupportedLibraries)
+    .then(()=>{
+      /* loaded */
+    })
+    .catch(e=>console.warn(e))
+
+  ```
+
+  - *unloadExternalLibraries([LIBRARY_NAME_1,LIBRARY_NAME_2])* unloading the libraries (should be called on shutdown).
+
+  - **[PLUGINNAME]** returns a plugin handler. This would be how to interface with the plugins.
+
+    
+    - *blink(sec?:number)* : Function that causes the floating widget to blink, attempt to grab user attention
+    - *shutdown()* : Function that causes the widget to shutdown dynamically. (triggers onShutdown callback)
+    - *onShutdown(callback)* : Attaches a callback function, which is called when the plugin is shutdown.
+
+    ```js
+    const pluginHandler = window.interactiveViewer.pluginControl[PLUGINNAME]
+
+    const subscription = window.interactiveViewer.metadata.selectedTemplateBSubject.subscribe(template=>console.log(template))
+
+    fetch(`http://YOUR_BACKEND.com/API_ENDPOINT`)
+      .then(data=>pluginHandler.blink(20))
+
+    pluginHandler.onShutdown(()=>{
+      subscription.unsubscribe()
+    })
+    ```
+
+------
+
+window.nehubaViewer
+---
+
+nehuba object, exposed if user would like to use it
+
+-------
+
+window.viewer
+---
+
+neuroglancer object, exposed if user would like to use it
\ No newline at end of file
diff --git a/src/plugin_examples/server.js b/src/plugin_examples/server.js
new file mode 100644
index 0000000000000000000000000000000000000000..ce14bf6949c65e7d7ebbabe6885371957f4785d5
--- /dev/null
+++ b/src/plugin_examples/server.js
@@ -0,0 +1,14 @@
+const express = require('express')
+
+const app = express()
+
+const cors = (req,res,next)=>{
+  res.setHeader('Access-Control-Allow-Origin','*')
+  next()
+}
+
+app.use(cors,express.static(__dirname))
+
+app.listen(10080,()=>{
+  console.log(`listening on 10080, serving ${__dirname}`)
+})
\ No newline at end of file
diff --git a/src/plugin_examples/testPlugin/manifest.json b/src/plugin_examples/testPlugin/manifest.json
new file mode 100644
index 0000000000000000000000000000000000000000..f59a172f48d5743665ef42219d84da73292e2bca
--- /dev/null
+++ b/src/plugin_examples/testPlugin/manifest.json
@@ -0,0 +1 @@
+{"name":"fzj.xg.test","type":"plugin","templateURL":"http://localhost:10080/testPlugin/template.html","scriptURL":"http://localhost:10080/testPlugin/script.js"}
diff --git a/src/plugin_examples/testPlugin/script.js b/src/plugin_examples/testPlugin/script.js
new file mode 100644
index 0000000000000000000000000000000000000000..e805a1924e712fdd43e1bd4d8d98bf2267c76893
--- /dev/null
+++ b/src/plugin_examples/testPlugin/script.js
@@ -0,0 +1,9 @@
+(()=>{
+  setTimeout(()=>{
+    const el = document.getElementById('testplugin-id')
+    const newel = document.createElement('div')
+    newel.innerHTML = `hello new owrld`
+    el.appendChild(newel)
+    
+  },100)
+})()
\ No newline at end of file
diff --git a/src/plugin_examples/testPlugin/template.html b/src/plugin_examples/testPlugin/template.html
new file mode 100644
index 0000000000000000000000000000000000000000..14e0ebf75e7171960e9d2830e6cfe720dc650819
--- /dev/null
+++ b/src/plugin_examples/testPlugin/template.html
@@ -0,0 +1,3 @@
+<div id = "testplugin-id">
+  hello world
+</div>
diff --git a/src/res/css/extra_styles.css b/src/res/css/extra_styles.css
new file mode 100644
index 0000000000000000000000000000000000000000..aa4a5e77b82afdc6f987fdebd63cdb56e061aabb
--- /dev/null
+++ b/src/res/css/extra_styles.css
@@ -0,0 +1,120 @@
+html
+{
+  width:100%;
+  height:100%;
+}
+body
+{
+  width:100%;
+  height:100%;
+  margin:0;
+  border:0;
+
+  /* required for glyphicon tooltip directives */
+  overflow:hidden;
+}
+div.scale-bar-container
+{
+  text-align: center;
+  background-color: rgba(0,0,0,.3);
+  position: absolute;
+  left: 1em;
+  bottom: 1em;
+  padding: 2px;
+  font-weight: 700;
+  pointer-events: none;
+}
+
+div.scale-bar
+{
+  min-height: 1ex;
+  background-color: #fff;
+  padding: 0;
+  margin: 0;
+  margin-top: 2px;
+}
+div.neuroglancer-rendered-data-panel
+{
+  position:relative;
+}
+
+ul#statusContainer
+{
+  display:none;
+}
+
+.inputSearchContainer
+{
+  background:none;
+  box-shadow:none;
+  border:none;
+  /* width:25em; */
+  max-width:999999px;
+}
+.inputSearchContainer .popover-arrow
+{
+  display:none;
+}
+
+.inputSearchContainer .popover-content.popover-body
+{
+  padding:0;
+  max-width:999999px;
+}
+
+.mute-text
+{
+  opacity:0.8;
+}
+
+div.scale-bar-container
+{
+  font-weight:500;
+  color: #1a1a1a;
+  background-color:hsla(0,0%,80%,0.5);
+}
+
+label.perspective-panel-show-slice-views
+{
+  visibility: hidden;
+}
+
+label.perspective-panel-show-slice-views:hover
+{
+  text-decoration: underline
+}
+
+[darktheme="false"] .neuroglancer-panel
+{
+  border:2px solid rgba(255,255,255,0.9);
+}
+
+[darktheme="true"] .neuroglancer-panel
+{
+  border:2px solid rgba(30,30,30,0.9);
+}
+
+label.perspective-panel-show-slice-views:before
+{
+  margin-left: .2em;
+  content: "show / hide frontal octant";
+  visibility: visible;
+  pointer-events: all;
+  color: #337ab7;
+}
+
+[darktheme="true"] .scale-bar-container
+{
+  color:#f2f2f2;
+  background-color:hsla(0,0%,60%,0.2);
+}
+
+span.regionSelected
+{
+  color : #dbb556
+}
+
+markdown-dom pre code
+{
+  white-space:pre;
+}
\ No newline at end of file
diff --git a/src/res/css/plugin_styles.css b/src/res/css/plugin_styles.css
new file mode 100644
index 0000000000000000000000000000000000000000..3fbdf34578aa805573416ae02de95cb2d3b472dd
--- /dev/null
+++ b/src/res/css/plugin_styles.css
@@ -0,0 +1,35 @@
+/* layout */
+
+[plugincontainer] .btn,
+[plugincontainer] .input-group-addon,
+[plugincontainer] input[type="text"],
+[plugincontainer] .panel
+{
+  border-radius:0px;
+  border:none;
+}
+
+[plugincontainer] .btn
+{
+  opacity : 0.9;
+  transition: opacity 0.3s ease, transform 0.3s ease;
+  box-shadow : rgba(5, 5, 5, 0.1) 0px 4px 6px 0px;
+}
+
+[plugincontainer] .btn:hover
+{
+  opacity:1.0;
+  transform:translateY(-5%);
+  box-shadow : rgba(5, 5, 5, 0.25) 0px 4px 6px 0px;
+}
+
+/* colour */
+[darktheme="true"] [plugincontainer] .btn,
+[darktheme="true"] [plugincontainer] .input-group-addon,
+[darktheme="true"] [plugincontainer] input[type="text"],
+[darktheme="true"] [plugincontainer] .panel
+{
+  background-color:rgba(80,80,80,0.8);
+  color:white;
+}
+
diff --git a/src/services/stateStore.service.ts b/src/services/stateStore.service.ts
index 421aa33b821d40685133abc5a3eae4e283b045c2..67348cba88ae85204489c985308b07a6a0a17ac2 100644
--- a/src/services/stateStore.service.ts
+++ b/src/services/stateStore.service.ts
@@ -26,6 +26,9 @@ export const OPEN_SIDE_PANEL = `OPEN_SIDE_PANEL`
 
 export const MOUSE_OVER_SEGMENT = `MOUSE_OVER_SEGMENT`
 
+export const FETCHED_PLUGIN_MANIFESTS = `FETCHED_PLUGIN_MANIFESTS`
+export const LAUNCH_PLUGIN = `LAUNCH_PLUGIN`
+
 export interface ViewerStateInterface{
   fetchedTemplates : any[]
 
@@ -113,7 +116,8 @@ export function viewerState(state:ViewerStateInterface,action:AtlasAction){
         dedicatedView : null
       })
     case FETCHED_TEMPLATES : {
-      return Object.assign({},state,{fetchedTemplates:action.fetchedTemplate})
+      return Object.assign({},state,{
+        fetchedTemplates:action.fetchedTemplate})
     }
     case CHANGE_NAVIGATION : {
       return Object.assign({},state,{navigation : action.navigation})
diff --git a/src/ui/banner/banner.component.ts b/src/ui/banner/banner.component.ts
index 4947e11115011942aefa58efb7c4f956cc3af49f..78ac4ccfc5b0072d04517d32c690bdc761aa5a0f 100644
--- a/src/ui/banner/banner.component.ts
+++ b/src/ui/banner/banner.component.ts
@@ -134,6 +134,10 @@ export class AtlasBanner implements OnDestroy{
     return ''
   }
 
+  getChildren(item:any){
+    return item.children
+  }
+
   filterTreeBySearch(node:any):boolean{
     return this.filterNameBySearchPipe.transform([node.name],this.searchTerm)
   }
diff --git a/src/ui/banner/banner.template.html b/src/ui/banner/banner.template.html
index bd06b9581b9a47632cc777c87f7c74d5270448eb..6b0e30b92996bfa04e4ece94d0dd737357f3211e 100644
--- a/src/ui/banner/banner.template.html
+++ b/src/ui/banner/banner.template.html
@@ -34,6 +34,8 @@
       placeholder="Regions"/>
       
   </div>
+  <plugin-banner>
+  </plugin-banner>
 </div>
 
 <ng-template #searchRegionTemplate>
@@ -44,7 +46,7 @@
         <span (click) = "clearRegions($event)" *ngIf = "selectedRegions.length > 0" class = "btn btn-link">clear all</span>
       </div>
       <tree
-        *ngFor = "let child of selectedParcellation.regions"
+        *ngFor = "let child of (selectedParcellation.regions | treeSearch : filterTreeBySearch.bind(this) : getChildren )"
         (mouseclicktree) = "handleClickRegion($event)"
         [renderNode]="(displayTreeNode).bind(this)"
         [searchFilter]="(filterTreeBySearch).bind(this)"
diff --git a/src/ui/nehubaContainer/nehubaContainer.component.ts b/src/ui/nehubaContainer/nehubaContainer.component.ts
index 294c232785a9d3a5f7ca67ddbffeeff67ae3ad7e..5f25d436aad8d6540baee01b968f0a7ddd1a61a9 100644
--- a/src/ui/nehubaContainer/nehubaContainer.component.ts
+++ b/src/ui/nehubaContainer/nehubaContainer.component.ts
@@ -1,11 +1,11 @@
 import { Component, ViewChild, ViewContainerRef, ComponentFactoryResolver, ComponentFactory, ComponentRef, OnInit, OnDestroy, ElementRef, AfterViewInit } from "@angular/core";
 import { NehubaViewerUnit } from "./nehubaViewer/nehubaViewer.component";
 import { Store, select } from "@ngrx/store";
-import { ViewerStateInterface, safeFilter, SELECT_REGIONS, getLabelIndexMap, DataEntry, CHANGE_NAVIGATION, isDefined, SPATIAL_GOTO_PAGE, MOUSE_OVER_SEGMENT } from "../../services/stateStore.service";
-import { Observable, Subscription, fromEvent, combineLatest } from "rxjs";
+import { ViewerStateInterface, safeFilter, SELECT_REGIONS, getLabelIndexMap, DataEntry, CHANGE_NAVIGATION, isDefined, MOUSE_OVER_SEGMENT } from "../../services/stateStore.service";
+import { Observable, Subscription, fromEvent, combineLatest, merge } from "rxjs";
 import { filter,map, take, scan, debounceTime, distinctUntilChanged } from "rxjs/operators";
 import * as export_nehuba from 'export_nehuba'
-import { AtlasViewerDataService } from "../../atlasViewer/atlasViewer.dataService.service";
+import { AtlasViewerAPIServices } from "../../atlasViewer/atlasViewer.apiService.service";
 
 @Component({
   selector : 'ui-nehuba-container',
@@ -32,7 +32,8 @@ export class NehubaContainer implements OnInit,OnDestroy,AfterViewInit{
   private selectedRegions$ : Observable<any[]>
   private dedicatedView$ : Observable<string|null>
   private fetchedSpatialDatasets$ : Observable<any[]>
-  public onHoverSegment$ : Observable<string>
+  public onHoverSegmentName$ : Observable<string>
+  public onHoverSegment$ : Observable<any>
 
   private navigationChanges$ : Observable<any>
   private redrawObservable$ : Observable<any>
@@ -53,6 +54,7 @@ export class NehubaContainer implements OnInit,OnDestroy,AfterViewInit{
 
 
   constructor(
+    private apiService :AtlasViewerAPIServices,
     private csf:ComponentFactoryResolver,
     private store : Store<ViewerStateInterface>,
     private elementRef : ElementRef
@@ -111,7 +113,28 @@ export class NehubaContainer implements OnInit,OnDestroy,AfterViewInit{
       distinctUntilChanged()
     )
 
+    const segmentsUnchangedChanged = (s1,s2)=>
+      !(typeof s1 === typeof s2 ?
+        typeof s2 === 'undefined' ?
+          false :
+          typeof s2 === 'number' ?
+            s2 !== s1 :
+            s1 === s2 ?
+              false :
+              s1 === null || s2 === null ?
+                true :
+                s2.name !== s1.name :
+        true)
+    
+
     this.onHoverSegment$ = this.store.pipe(
+      select('uiState'),
+      filter(state=>isDefined(state)),
+      map(state=>state.mouseOverSegment),
+      distinctUntilChanged(segmentsUnchangedChanged)
+    )
+
+    this.onHoverSegmentName$ = this.store.pipe(
       select('uiState'),
       filter(state=>isDefined(state)),
       map(state=>state.mouseOverSegment ?
@@ -237,12 +260,10 @@ export class NehubaContainer implements OnInit,OnDestroy,AfterViewInit{
 
   handleMouseEnterLandmark(spatialData:any){
     spatialData.highlight = true
-    // console.log('mouseover',spatialData)
   }
 
   handleMouseLeaveLandmark(spatialData:any){
     spatialData.highlight = false
-    // console.log('mouseleave')
   }
 
   private getNMToOffsetPixelFn(){
@@ -344,6 +365,9 @@ export class NehubaContainer implements OnInit,OnDestroy,AfterViewInit{
   spatialSearchPagination : number = 0
 
   private createNewNehuba(template:any){
+
+    this.apiService.interactiveViewer.viewerHandle = null
+
     this.viewerLoaded = true
     this.container.clear()
     this.cr = this.container.createComponent(this.nehubaViewerFactory)
@@ -357,6 +381,78 @@ export class NehubaContainer implements OnInit,OnDestroy,AfterViewInit{
     this.nehubaViewerSubscriptions.push(
       this.nehubaViewer.mouseoverSegmentEmitter.subscribe(this.handleEmittedMouseoverSegment.bind(this))
     )
+
+    this.setupViewerHandleApi()
+  }
+
+  private setupViewerHandleApi(){
+    this.apiService.interactiveViewer.viewerHandle = {
+      setNavigationLoc : (coord,realSpace?)=>this.nehubaViewer.setNavigationState({
+        position : coord,
+        positionReal : typeof realSpace !== 'undefined' ? realSpace : true
+      }),
+      /* TODO introduce animation */
+      moveToNavigationLoc : (coord,realSpace?)=>this.nehubaViewer.setNavigationState({
+        position : coord,
+        positionReal : typeof realSpace !== 'undefined' ? realSpace : true
+      }),
+      setNavigationOri : (quat)=>this.nehubaViewer.setNavigationState({
+        orientation : quat
+      }),
+      /* TODO introduce animation */
+      moveToNavigationOri : (quat)=>this.nehubaViewer.setNavigationState({
+        orientation : quat
+      }),
+      showSegment : (labelIndex) => {
+        if(!this.selectedRegionIndexSet.has(labelIndex)) 
+          this.store.dispatch({
+            type : SELECT_REGIONS,
+            selectRegions :  [labelIndex, ...this.selectedRegionIndexSet]
+          })
+      },
+      hideSegment : (labelIndex) => {
+        if(this.selectedRegionIndexSet.has(labelIndex)){
+          this.store.dispatch({
+            type :SELECT_REGIONS,
+            selectRegions : [...this.selectedRegionIndexSet].filter(num=>num!==labelIndex)
+          })
+        }
+      },
+      showAllSegments : () => {
+        this.store.dispatch({
+          type : SELECT_REGIONS,
+          selectRegions : this.regionsLabelIndexMap.keys()
+        })
+      },
+      hideAllSegments : ()=>{
+        this.store.dispatch({
+          type : SELECT_REGIONS,
+          selectRegions : []
+        })
+      },
+      segmentColourMap : new Map(),
+      applyColourMap : (map)=>{
+        /* TODO to be implemented */
+      },
+      loadLayer : (layerObj)=>this.nehubaViewer.loadLayer(layerObj),
+      removeLayer : (condition)=>this.nehubaViewer.removeLayer(condition),
+      setLayerVisibility : (condition,visible)=>this.nehubaViewer.setLayerVisibility(condition,visible),
+      mouseEvent : merge(
+        fromEvent(this.nehubaViewer.elementRef.nativeElement,'click').pipe(
+          map((ev:MouseEvent)=>({eventName :'click',event:ev}))
+        ),
+        fromEvent(this.nehubaViewer.elementRef.nativeElement,'mousemove').pipe(
+          map((ev:MouseEvent)=>({eventName :'mousemove',event:ev}))
+        ),
+        fromEvent(this.nehubaViewer.elementRef.nativeElement,'mousedown').pipe(
+          map((ev:MouseEvent)=>({eventName :'mousedown',event:ev}))
+        ),
+        fromEvent(this.nehubaViewer.elementRef.nativeElement,'mouseup').pipe(
+          map((ev:MouseEvent)=>({eventName :'mouseup',event:ev}))
+        ),
+      ) ,
+      mouseOverNehuba : this.onHoverSegment$
+    }
   }
 
   handleEmittedMouseoverSegment(emitted : any | number | null){
diff --git a/src/ui/nehubaContainer/nehubaContainer.style.css b/src/ui/nehubaContainer/nehubaContainer.style.css
index 1daed932e5ae9ec9cc79eb212904f62be1cb568b..1adeb5bccea9afe544dfe94606782dc3e78d2c31 100644
--- a/src/ui/nehubaContainer/nehubaContainer.style.css
+++ b/src/ui/nehubaContainer/nehubaContainer.style.css
@@ -86,3 +86,7 @@ div[landmarkMasterContainer] > div > [landmarkContainer] > div
   margin-top:-1em;
 }
 
+small[onHoverSegment]
+{
+  margin-left:2em;
+}
\ No newline at end of file
diff --git a/src/ui/nehubaContainer/nehubaContainer.template.html b/src/ui/nehubaContainer/nehubaContainer.template.html
index 6f01a65b45dd8df5584eb5715690bb5c557acc7b..5e810777cfa0d8dd7ff07a0d5312fd06ec9533d5 100644
--- a/src/ui/nehubaContainer/nehubaContainer.template.html
+++ b/src/ui/nehubaContainer/nehubaContainer.template.html
@@ -89,8 +89,8 @@
         {{ mouseCoord }}
       </small> 
       <br />
-      <small>
-        &nbsp;&nbsp;{{ onHoverSegment$ | async }}
+      <small onHoverSegment>
+        {{ onHoverSegment$ | async }}
       </small>
     </div>
   </div>
diff --git a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts b/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts
index d69483896f8ac1a707e9889287e64c3e17928dab..340949cfa0cdc9ac196a588950b2659aa0c02bc5 100644
--- a/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts
+++ b/src/ui/nehubaContainer/nehubaViewer/nehubaViewer.component.ts
@@ -1,4 +1,4 @@
-import { Component, AfterViewInit, OnDestroy, Output, EventEmitter } from "@angular/core";
+import { Component, AfterViewInit, OnDestroy, Output, EventEmitter, ElementRef } from "@angular/core";
 import * as export_nehuba from 'export_nehuba'
 
 import 'export_nehuba/dist/min/chunk_worker.bundle.js'
@@ -45,6 +45,10 @@ export class NehubaViewerUnit implements AfterViewInit,OnDestroy{
     this._s8$
   ]
 
+  constructor(public elementRef:ElementRef){
+    
+  }
+
   private _parcellationId : string
 
   get parcellationId(){
@@ -130,6 +134,13 @@ export class NehubaViewerUnit implements AfterViewInit,OnDestroy{
     this.loadLayer(_)
   }
 
+  public setLayerVisibility(condition:{name:string|RegExp},visible:boolean){
+    const viewer = this.nehubaViewer.ngviewer
+    viewer.layerManager.managedLayers
+      .filter(l=>this.filterLayers(l,condition))
+      .map(layer=>layer.setVisible(visible))
+  }
+
   public remove3DLandmarks(){
     this.removeLayer({
       name : /vtk-[0-9]/
diff --git a/src/ui/pluginBanner/pluginBanner.component.ts b/src/ui/pluginBanner/pluginBanner.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4081d39d2ccde043eed5641ba165a52a6754abeb
--- /dev/null
+++ b/src/ui/pluginBanner/pluginBanner.component.ts
@@ -0,0 +1,18 @@
+import { Component } from "@angular/core";
+import { PluginServices } from "../../atlasViewer/atlasViewer.pluginService.service";
+
+
+@Component({
+  selector : 'plugin-banner',
+  templateUrl : './pluginBanner.template.html',
+  styleUrls : [
+    `./pluginBanner.style.css`
+  ]
+})
+
+export class PluginBannerUI{
+  
+  constructor(public pluginServices:PluginServices){
+
+  }
+}
\ No newline at end of file
diff --git a/src/ui/pluginBanner/pluginBanner.style.css b/src/ui/pluginBanner/pluginBanner.style.css
new file mode 100644
index 0000000000000000000000000000000000000000..0f51586a46488a894fe1cf5846b071b17fc81e98
--- /dev/null
+++ b/src/ui/pluginBanner/pluginBanner.style.css
@@ -0,0 +1,36 @@
+:host
+{
+  margin-left:1em;
+}
+
+.btn
+{
+  border-radius: 0px;
+  border:none;
+}
+
+.btn
+{
+  opacity : 0.9;
+  transition: opacity 0.3s ease, transform 0.3s ease;
+  box-shadow : rgba(5, 5, 5, 0.1) 0px 4px 6px 0px;
+}
+
+.btn:hover
+{
+  cursor:default;
+  opacity:1.0;
+  transform:translateY(-5%);
+  box-shadow : rgba(5, 5, 5, 0.25) 0px 4px 6px 0px;
+}
+
+.btn-default
+{
+  background-color:rgba(255,255,255,0.8);
+}
+
+:host-context([darktheme="true"]) .btn-default
+{
+  background-color:rgba(80,80,80,0.8);
+  color:white;
+}
\ No newline at end of file
diff --git a/src/ui/pluginBanner/pluginBanner.template.html b/src/ui/pluginBanner/pluginBanner.template.html
new file mode 100644
index 0000000000000000000000000000000000000000..849bc291b6b62972a6bcbf120e5fd85141592e1c
--- /dev/null
+++ b/src/ui/pluginBanner/pluginBanner.template.html
@@ -0,0 +1,6 @@
+<div 
+  *ngFor = "let plugin of pluginServices.fetchedPluginManifests"
+  (click) = "pluginServices.launchPlugin(plugin)"
+  class = "btn btn-default">
+  {{ plugin.name }}
+</div>
\ No newline at end of file
diff --git a/src/ui/ui.module.ts b/src/ui/ui.module.ts
index 15f0e02678b32abaaf4b083fb57d58fdcbd922a5..5fbbf3def24dc128e789455a0a63ab973c60fadc 100644
--- a/src/ui/ui.module.ts
+++ b/src/ui/ui.module.ts
@@ -22,6 +22,9 @@ import { FilterDataEntriesbyType } from "../util/pipes/filterDataEntriesByType.p
 import { DedicatedViewer } from "./fileviewer/dedicated/dedicated.component";
 import { LandmarkUnit } from "./nehubaContainer/landmarkUnit/landmarkUnit.component";
 import { SafeStylePipe } from "../util/pipes/safeStyle.pipe";
+import { PluginBannerUI } from "./pluginBanner/pluginBanner.component";
+import { AtlasBanner } from "./banner/banner.component";
+import { PopoverModule } from "ngx-bootstrap/popover";
 
 
 @NgModule({
@@ -30,7 +33,9 @@ import { SafeStylePipe } from "../util/pipes/safeStyle.pipe";
     FormsModule,
     BrowserModule,
     LayoutModule,
-    ComponentsModule
+    ComponentsModule,
+
+    PopoverModule.forRoot(),
   ],
   declarations : [
     NehubaContainer,
@@ -42,6 +47,8 @@ import { SafeStylePipe } from "../util/pipes/safeStyle.pipe";
     LineChart,
     DedicatedViewer,
     LandmarkUnit,
+    AtlasBanner,
+    PluginBannerUI,
 
     /* pipes */
     GroupDatasetByRegion,
@@ -61,6 +68,8 @@ import { SafeStylePipe } from "../util/pipes/safeStyle.pipe";
     DataBrowserUI
   ],
   exports : [
+    AtlasBanner,
+    PluginBannerUI,
     NehubaContainer,
     NehubaViewerUnit,
     DataBrowserUI,
diff --git a/src/util/pipes/treeSearch.pipe.ts b/src/util/pipes/treeSearch.pipe.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5f26c05c662b0caae9b8a0d2c5736a0aa0413f6b
--- /dev/null
+++ b/src/util/pipes/treeSearch.pipe.ts
@@ -0,0 +1,15 @@
+import { PipeTransform, Pipe } from "@angular/core";
+
+
+@Pipe({
+  name : 'treeSearch'
+})
+
+export class TreeSearchPipe implements PipeTransform{
+  public transform(array:any[],filterFn:(item:any)=>boolean,getChildren:(item:any)=>any[]):any[]{
+    const transformSingle = (item:any):boolean=>
+      filterFn(item) ||
+      getChildren(item).some(transformSingle)
+    return array.filter(transformSingle)
+  }
+}
\ No newline at end of file