diff --git a/src/app/components/shared/_dialogs/cloud-configuration/cloud-configuration.component.ts b/src/app/components/shared/_dialogs/cloud-configuration/cloud-configuration.component.ts
index 94f2117a5e6498a1234e130d54c39cc4b0d2b192..1115225d8b676ee6c8b855373117f2d0d9e5fe3f 100644
--- a/src/app/components/shared/_dialogs/cloud-configuration/cloud-configuration.component.ts
+++ b/src/app/components/shared/_dialogs/cloud-configuration/cloud-configuration.component.ts
@@ -1,9 +1,9 @@
 import { Component, Input, OnInit } from '@angular/core';
 import { TranslateService } from '@ngx-translate/core';
 import { TagCloudComponent } from '../../tag-cloud/tag-cloud.component';
-import { TagCloudMetaDataCount } from '../../tag-cloud/tag-cloud.data-manager';
 import { CloudParameters, CloudTextStyle } from '../../tag-cloud/tag-cloud.interface';
 import { WeightClass } from './weight-class.interface';
+import { TagCloudMetaDataCount } from '../../../../services/util/tag-cloud-data.service';
 
 @Component({
   selector: 'app-cloud-configuration',
diff --git a/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.html b/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.html
index 06aa818ba806b9e31e753813169f82e597594369..f621877ae21001e348de4ff1dc69bf4c9ead0312 100644
--- a/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.html
+++ b/src/app/components/shared/_dialogs/topic-cloud-administration/topic-cloud-administration.component.html
@@ -108,10 +108,10 @@
               Spacy labels
             </mat-panel-title>
           </mat-expansion-panel-header>
-  
+
           <mat-tab-group animationDuration="0ms" mat-stretch-tabs mat-align-tabs="center">
             <mat-tab label="{{'topic-cloud-dialog.german' | translate}}">
-              <mat-selection-list [(ngModel)]="wantedLabels.de">
+              <mat-selection-list *ngIf="wantedLabels" [(ngModel)]="wantedLabels.de">
 
                 <mat-option class="color-on-surface" (click)="selectAllDE(); allSelectedDE = !allSelectedDE">
                   <mat-label>
@@ -126,7 +126,7 @@
               </mat-selection-list>
             </mat-tab>
             <mat-tab label="{{'topic-cloud-dialog.english' | translate}}">
-              <mat-selection-list [(ngModel)]="wantedLabels.en">
+              <mat-selection-list *ngIf="wantedLabels" [(ngModel)]="wantedLabels.en">
 
                 <mat-option class="color-on-surface" (click)="selectAllEN(); allSelectedEN = !allSelectedEN">
                   <mat-label>
diff --git a/src/app/components/shared/comment-list/comment-list.component.ts b/src/app/components/shared/comment-list/comment-list.component.ts
index 2e25c141dbb9a8e7b2d708368cc82f26b369c115..8bf801d639de9196d687dd999531981f0cc3cdf0 100644
--- a/src/app/components/shared/comment-list/comment-list.component.ts
+++ b/src/app/components/shared/comment-list/comment-list.component.ts
@@ -140,7 +140,7 @@ export class CommentListComponent implements OnInit, OnDestroy {
       dialogRef.componentInstance.roomId = this.room.id;
     });
     this.eventService.on<string>('setTagConfig').subscribe(tag => {
-      this.clickedOnTag(tag);
+      this.clickedOnKeyword(tag);
     });
     nav('tags', () => {
       const updRoom = JSON.parse(JSON.stringify(this.room));
@@ -459,7 +459,6 @@ export class CommentListComponent implements OnInit, OnDestroy {
       this.sortComments(this.currentSort);
       return;
     }
-    console.log(compare);
     this.filteredComments = this.commentsFilteredByTime.filter(c => {
       switch (type) {
         case this.correct:
diff --git a/src/app/components/shared/header/header.component.ts b/src/app/components/shared/header/header.component.ts
index 65d387666d2b9ac11a2935dfbf73971d1b319a73..c3842163ab6ec31498753ccb4abf41f0ae106357 100644
--- a/src/app/components/shared/header/header.component.ts
+++ b/src/app/components/shared/header/header.component.ts
@@ -23,7 +23,7 @@ import { MotdService } from '../../../services/http/motd.service';
 import { TopicCloudFilterComponent } from '../_dialogs/topic-cloud-filter/topic-cloud-filter.component';
 import { RoomService } from '../../../services/http/room.service';
 import { Room } from '../../../models/room';
-import { TagCloudMetaData } from '../tag-cloud/tag-cloud.data-manager';
+import { TagCloudMetaData } from '../../../services/util/tag-cloud-data.service';
 
 @Component({
   selector: 'app-header',
diff --git a/src/app/components/shared/shared.module.ts b/src/app/components/shared/shared.module.ts
index 0fe7715ac88a1eada7caf515b44614ca0fbeed45..9e1fa76e707e4fe232456bbe490910b5f1b96b51 100644
--- a/src/app/components/shared/shared.module.ts
+++ b/src/app/components/shared/shared.module.ts
@@ -36,6 +36,7 @@ import { TopicCloudAdministrationComponent } from './_dialogs/topic-cloud-admini
 import { TopicDialogCommentComponent } from './dialog/topic-dialog-comment/topic-dialog-comment.component';
 import { TopicCloudFilterComponent } from './_dialogs/topic-cloud-filter/topic-cloud-filter.component';
 import { SpacyDialogComponent } from './_dialogs/spacy-dialog/spacy-dialog.component';
+import { TagCloudPopUpComponent } from './tag-cloud/tag-cloud-pop-up/tag-cloud-pop-up.component';
 
 @NgModule({
   imports: [
@@ -77,7 +78,8 @@ import { SpacyDialogComponent } from './_dialogs/spacy-dialog/spacy-dialog.compo
     TopicCloudAdministrationComponent,
     TopicDialogCommentComponent,
     TopicCloudFilterComponent,
-    SpacyDialogComponent
+    SpacyDialogComponent,
+    TagCloudPopUpComponent
   ],
     exports: [
         RoomJoinComponent,
@@ -93,7 +95,8 @@ import { SpacyDialogComponent } from './_dialogs/spacy-dialog/spacy-dialog.compo
         CommentComponent,
         DialogActionButtonsComponent,
         UserBonusTokenComponent,
-        CloudConfigurationComponent
+        CloudConfigurationComponent,
+        TagCloudPopUpComponent
     ]
 })
 export class SharedModule {
diff --git a/src/app/components/shared/tag-cloud/tag-cloud-pop-up/tag-cloud-pop-up.component.html b/src/app/components/shared/tag-cloud/tag-cloud-pop-up/tag-cloud-pop-up.component.html
new file mode 100644
index 0000000000000000000000000000000000000000..65e367adb959663a8e22fe6966624e26368b9c44
--- /dev/null
+++ b/src/app/components/shared/tag-cloud/tag-cloud-pop-up/tag-cloud-pop-up.component.html
@@ -0,0 +1,39 @@
+<div #popupContainer
+     class="popupContainer"
+     (focusout)="onFocus($event)"
+      tabindex="0">
+  <span>
+    <mat-icon matTooltip="{{'tag-cloud.overview-question-topic-tooltip' | translate}}">comment</mat-icon>
+    <p>
+      {{tagData && tagData.comments.length}}
+    </p>
+    <mat-icon matTooltip="{{'tag-cloud.overview-questioners-topic-tooltip' | translate}}">person</mat-icon>
+    <p>
+      {{tagData && tagData.distinctUsers.size}}
+    </p>
+    <mat-icon matTooltip="{{'tag-cloud.upvote-topic' | translate}}">thumb_up</mat-icon>
+    <p>
+      {{tagData && tagData.cachedUpVotes}}
+    </p>
+    <mat-icon matTooltip="{{'tag-cloud.downvote-topic' | translate}}">thumb_down</mat-icon>
+    <p>
+      {{tagData && tagData.cachedDownVotes}}
+    </p>
+    <button *ngIf="user && user.role >= 1" mat-button (click)="addBlacklistWord()">
+      <mat-icon matTooltip="{{'tag-cloud.blacklist-topic' | translate}}">gavel</mat-icon>
+    </button>
+  </span>
+  <br>
+  <span>
+    <mat-icon matTooltip="{{'tag-cloud.period-since-first-comment' | translate}}">date_range</mat-icon>
+    <p>
+      {{timePeriodText}}
+    </p>
+  </span>
+  <div *ngIf="categories && categories.length">
+    <p>Kategorien:</p>
+    <ul>
+      <li *ngFor="let category of categories">{{category}}</li>
+    </ul>
+  </div>
+</div>
diff --git a/src/app/components/shared/tag-cloud/tag-cloud-pop-up/tag-cloud-pop-up.component.scss b/src/app/components/shared/tag-cloud/tag-cloud-pop-up/tag-cloud-pop-up.component.scss
new file mode 100644
index 0000000000000000000000000000000000000000..057e27b57a667a064649da4332cadcb7b5a40733
--- /dev/null
+++ b/src/app/components/shared/tag-cloud/tag-cloud-pop-up/tag-cloud-pop-up.component.scss
@@ -0,0 +1,118 @@
+$popup-arrow-size: 20px;
+$popup-arrow-border-size: 2px;
+$popup-arrow-half-size: $popup-arrow-size / 2;
+$popup-arrow-offset: $popup-arrow-half-size + $popup-arrow-border-size;
+$header-size: 67px;
+
+.popupContainer {
+  visibility: hidden;
+  border-radius: 25px;
+  border: 2px solid #000;
+  background-color: var(--dialog);
+  padding: $popup-arrow-half-size;
+  position: absolute;
+  box-shadow: 0 0 10px var(--dialog);
+  box-sizing: border-box;
+  z-index: 3;
+  color: var(--on-dialog);
+  transform: translateY(-$header-size);
+
+  &:focus {
+    outline: none;
+  }
+
+  &::after {
+    position: absolute;
+    content: '';
+    width: $popup-arrow-size;
+    height: $popup-arrow-size;
+    background-color: var(--dialog);
+    border-top: 0 solid #000;
+    border-right: $popup-arrow-border-size solid #000;
+    border-left: 0 solid #000;
+    border-bottom: $popup-arrow-border-size solid #000;
+  }
+
+  &.down {
+    visibility: unset;
+    transform: translate(-50%, calc(-100% - #{$header-size + $popup-arrow-offset}));
+
+    &::after {
+      top: 100%;
+      left: 50%;
+      margin-top: -$popup-arrow-half-size;
+      margin-left: -$popup-arrow-half-size;
+      transform: rotate(45deg);
+    }
+  }
+
+  &.left {
+    visibility: unset;
+    transform: translate($popup-arrow-offset, calc(-50% - #{$header-size}));
+
+    &::after {
+      top: 50%;
+      right: 100%;
+      margin-top: -$popup-arrow-half-size;
+      margin-right: -$popup-arrow-half-size;
+      transform: rotate(135deg);
+    }
+  }
+
+  &.up {
+    visibility: unset;
+    transform: translate(-50%, $popup-arrow-offset - $header-size);
+
+    &::after {
+      bottom: 100%;
+      left: 50%;
+      margin-bottom: -$popup-arrow-half-size;
+      margin-left: -$popup-arrow-half-size;
+      transform: rotate(225deg);
+    }
+  }
+
+  &.right {
+    visibility: unset;
+    transform: translate(calc(-100% - #{$popup-arrow-offset}), calc(-50% - #{$header-size}));
+
+    &::after {
+      top: 50%;
+      left: 100%;
+      margin-top: -$popup-arrow-half-size;
+      margin-left: -$popup-arrow-half-size;
+      transform: rotate(315deg);
+    }
+  }
+}
+
+div > p {
+  margin-bottom: 1px;
+}
+
+span {
+  margin-right: 5px;
+
+  & > mat-icon {
+    margin: -1px 0px 0px 12px;
+    vertical-align: middle;
+  }
+
+  & > p {
+    display: inline;
+    font-weight: 600;
+    vertical-align: middle;
+  }
+}
+
+ul {
+  margin: 0;
+}
+
+button {
+  color: var(--red);
+  min-width: unset;
+  margin-left: 0;
+  padding-left: 10px;
+  padding-right: 10px;
+}
diff --git a/src/app/components/shared/tag-cloud/tag-cloud-pop-up/tag-cloud-pop-up.component.spec.ts b/src/app/components/shared/tag-cloud/tag-cloud-pop-up/tag-cloud-pop-up.component.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..be4c0a85dfd377daf9509432ffd793ae52fed9fe
--- /dev/null
+++ b/src/app/components/shared/tag-cloud/tag-cloud-pop-up/tag-cloud-pop-up.component.spec.ts
@@ -0,0 +1,26 @@
+/*import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { TagCloudPopUpComponent } from './tag-cloud-pop-up.component';
+
+describe('TagCloudPopUpComponent', () => {
+  let component: TagCloudPopUpComponent;
+  let fixture: ComponentFixture<TagCloudPopUpComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      declarations: [ TagCloudPopUpComponent ]
+    })
+    .compileComponents();
+  });
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(TagCloudPopUpComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
+*/
diff --git a/src/app/components/shared/tag-cloud/tag-cloud-pop-up/tag-cloud-pop-up.component.ts b/src/app/components/shared/tag-cloud/tag-cloud-pop-up/tag-cloud-pop-up.component.ts
new file mode 100644
index 0000000000000000000000000000000000000000..de8d4523354b49e75560fa72208c99d5c2260e6b
--- /dev/null
+++ b/src/app/components/shared/tag-cloud/tag-cloud-pop-up/tag-cloud-pop-up.component.ts
@@ -0,0 +1,233 @@
+import { AfterViewInit, Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core';
+import { TranslateService } from '@ngx-translate/core';
+import { LanguageService } from '../../../../services/util/language.service';
+import { TagCloudComponent } from '../tag-cloud.component';
+import { AuthenticationService } from '../../../../services/http/authentication.service';
+import { User } from '../../../../models/user';
+import { TagCloudDataTagEntry } from "../../../../services/util/tag-cloud-data.service";
+
+const CLOSE_TIME = 1500;
+
+@Component({
+  selector: 'app-tag-cloud-pop-up',
+  templateUrl: './tag-cloud-pop-up.component.html',
+  styleUrls: ['./tag-cloud-pop-up.component.scss']
+})
+export class TagCloudPopUpComponent implements OnInit, AfterViewInit {
+
+  @Input() parent: TagCloudComponent;
+  @ViewChild('popupContainer') popupContainer: ElementRef;
+  tag: string;
+  tagData: TagCloudDataTagEntry;
+  categories: string[];
+  timePeriodText: string;
+  user: User;
+  private _popupHoverTimer: number;
+  private _popupCloseTimer: number;
+
+  constructor(private langService: LanguageService,
+              private translateService: TranslateService,
+              private authenticationService: AuthenticationService) {
+    this.langService.langEmitter.subscribe(lang => {
+      this.translateService.use(lang);
+    });
+  }
+
+  ngOnInit(): void {
+    this.timePeriodText = '...';
+    this.authenticationService.watchUser.subscribe(newUser => {
+      if (newUser) {
+        this.user = newUser;
+      }
+    });
+  }
+
+  ngAfterViewInit() {
+    const html = this.popupContainer.nativeElement as HTMLDivElement;
+    html.addEventListener('mouseenter', () => {
+      clearTimeout(this._popupCloseTimer);
+    });
+    html.addEventListener('mouseleave', () => {
+      this._popupCloseTimer = setTimeout(() => {
+        this.close();
+      }, CLOSE_TIME);
+    });
+  }
+
+  onFocus(event) {
+    if (!this.popupContainer.nativeElement.contains(event.target)) {
+      this.close();
+    }
+  }
+
+  leave(): void {
+    clearTimeout(this._popupHoverTimer);
+    this._popupCloseTimer = setTimeout(() => {
+      this.close();
+    }, CLOSE_TIME);
+  }
+
+  enter(elem: HTMLElement, tag: string, tagData: TagCloudDataTagEntry, hoverDelayInMs: number): void {
+    clearTimeout(this._popupCloseTimer);
+    clearTimeout(this._popupHoverTimer);
+    this._popupHoverTimer = setTimeout(() => {
+      this.tag = tag;
+      this.tagData = tagData;
+      this.categories = Array.from(tagData.categories.keys());
+      this.calculateDateText(() => {
+        this.position(elem);
+      });
+    }, hoverDelayInMs);
+  }
+
+  addBlacklistWord(): void {
+    this.parent.dataManager.blockWord(this.tag);
+    this.close();
+  }
+
+  close(): void {
+    const html = this.popupContainer.nativeElement as HTMLDivElement;
+    html.classList.remove('up', 'down', 'right', 'left');
+  }
+
+  private position(elem: HTMLElement) {
+    const html = this.popupContainer.nativeElement as HTMLDivElement;
+    const popup = html.getBoundingClientRect();
+    const tag = elem.getBoundingClientRect();
+    const boundingBox = elem.parentElement.getBoundingClientRect();
+    // calculate the free space to the left, right, top and bottom from tag
+    const spaceLeft = tag.x + tag.width / 2;
+    const spaceRight = boundingBox.right - tag.right + tag.width / 2;
+    const spaceTop = tag.y - boundingBox.y;
+    const spaceBottom = boundingBox.bottom - tag.bottom;
+    // set flags if tag is near bounding box
+    const isLeft = spaceLeft <= popup.width / 2.0;
+    const isRight = spaceRight <= popup.width / 2.0;
+    const isTop = spaceTop <= popup.height;
+    const isBottom = spaceBottom <= popup.height;
+
+    // try to make a decision where to place the popup outgoing from tag with checks if we are at a border of the viewport
+    enum PopupPosition {
+      top,
+      bottom,
+      left,
+      right
+    }
+
+    let dockingPosition;
+    if (isLeft && isTop && !isBottom && !isRight) {
+      dockingPosition = PopupPosition.right;
+    } else if (isTop && !isLeft && !isRight && !isBottom) {
+      dockingPosition = PopupPosition.bottom;
+    } else if (isRight && isTop && !isLeft && !isBottom) {
+      dockingPosition = PopupPosition.left;
+    } else if (isLeft && !isTop && !isRight && !isBottom) {
+      dockingPosition = PopupPosition.right;
+    } else if (!isLeft && !isTop && !isRight && !isBottom) {
+      // default docking when all sides offer enough space
+      dockingPosition = PopupPosition.top;
+    } else if (isRight && !isTop && !isLeft && !isBottom) {
+      dockingPosition = PopupPosition.left;
+    } else if (isLeft && isBottom && !isTop && !isRight) {
+      dockingPosition = PopupPosition.right;
+    } else if (!isLeft && isBottom && !isTop && !isRight) {
+      dockingPosition = PopupPosition.top;
+    } else if (!isLeft && isBottom && isTop && !isRight) {
+      dockingPosition = PopupPosition.left;
+    } else {
+      /*
+       * Find solution for small screens when all sides produce unpleasant results
+       */
+      dockingPosition = PopupPosition.top;
+    }
+    html.classList.remove('left', 'right', 'up', 'down');
+    if (dockingPosition === PopupPosition.bottom) {
+      html.style.top = tag.bottom + 'px';
+      html.style.left = tag.x + tag.width / 2 + 'px';
+      html.classList.add('up');
+    } else if (dockingPosition === PopupPosition.top) {
+      html.style.top = tag.y + 'px';
+      html.style.left = tag.x + tag.width / 2 + 'px';
+      html.classList.add('down');
+    } else if (dockingPosition === PopupPosition.left) {
+      html.style.top = tag.top + tag.height / 2 + 'px';
+      html.style.left = tag.x + 'px';
+      html.classList.add('right');
+    } else if (dockingPosition === PopupPosition.right) {
+      html.style.top = tag.top + tag.height / 2 + 'px';
+      html.style.left = tag.right + 'px';
+      html.classList.add('left');
+    }
+    html.focus();
+  }
+
+  private calculateDateText(afterInit: () => void): void {
+    const subscriber = (e: string) => {
+      this.timePeriodText = e;
+      if (afterInit) {
+        setTimeout(afterInit);
+      }
+    };
+    // @ts-ignore
+    const diffMs = Date.now() - Date.parse(this.tagData.firstTimeStamp);
+    const seconds = Math.floor(diffMs / 1_000);
+    if (seconds < 60) {
+      // few seconds
+      this.translateService.get('tag-cloud-popup.few-seconds').subscribe(subscriber);
+      return;
+    }
+    const minutes = Math.floor(seconds / 60);
+    if (minutes < 5) {
+      // few minutes
+      this.translateService.get('tag-cloud-popup.few-minutes').subscribe(subscriber);
+      return;
+    } else if (minutes < 60) {
+      // x minutes
+      this.translateService.get('tag-cloud-popup.some-minutes', {
+        minutes
+      }).subscribe(subscriber);
+      return;
+    }
+    const hours = Math.floor(minutes / 60);
+    if (hours === 1) {
+      // 1 hour
+      this.translateService.get('tag-cloud-popup.one-hour').subscribe(subscriber);
+      return;
+    } else if (hours < 24) {
+      // x hours
+      this.translateService.get('tag-cloud-popup.some-hours', {
+        hours
+      }).subscribe(subscriber);
+      return;
+    }
+    const days = Math.floor(hours / 24);
+    if (days === 1) {
+      // 1 day
+      this.translateService.get('tag-cloud-popup.one-day').subscribe(subscriber);
+      return;
+    } else if (days < 7) {
+      // x days
+      this.translateService.get('tag-cloud-popup.some-days', {
+        days
+      }).subscribe(subscriber);
+      return;
+    }
+    const weeks = Math.floor(days / 7);
+    if (weeks === 1) {
+      // 1 week
+      this.translateService.get('tag-cloud-popup.one-week').subscribe(subscriber);
+      return;
+    } else if (weeks < 12) {
+      // x weeks
+      this.translateService.get('tag-cloud-popup.some-weeks', {
+        weeks
+      }).subscribe(subscriber);
+      return;
+    }
+    const months = Math.floor(weeks / 4);
+    // x months
+    this.translateService.get('tag-cloud-popup.some-months', {
+      months
+    }).subscribe(subscriber);
+  }
+}
diff --git a/src/app/components/shared/tag-cloud/tag-cloud.component.html b/src/app/components/shared/tag-cloud/tag-cloud.component.html
index 509971aa8bae17c92eb112e1b9e6ad0cf07c01fe..6f6afd01783495837b2d1ac015d6a7644755fc2c 100644
--- a/src/app/components/shared/tag-cloud/tag-cloud.component.html
+++ b/src/app/components/shared/tag-cloud/tag-cloud.component.html
@@ -5,13 +5,14 @@
     </mat-drawer>
     <mat-drawer-content>
       <ars-fill ars-flex-box>
+        <app-tag-cloud-pop-up [parent]="this"></app-tag-cloud-pop-up>
         <div [ngClass]="{'hidden': !isLoading}" fxLayout="row" fxLayoutAlign="center center" fxFill>
           <mat-progress-spinner *ngIf="isLoading" mode="indeterminate"></mat-progress-spinner>
         </div>
         <angular-tag-cloud
+          id="tagCloudComponent"
           class="spacyTagCloud"
           (window:resize)="onResize($event)"
-          (afterInit)="initTagCloud()"
           (clicked)="openTags($event)"
           [data]="data"
           [width]="options.width"
diff --git a/src/app/components/shared/tag-cloud/tag-cloud.component.scss b/src/app/components/shared/tag-cloud/tag-cloud.component.scss
index adfc5f5d2534942cfe624e704b87e16e83ba393d..7304a3c38ffadd188d72504dc7e86d20eef15cb3 100644
--- a/src/app/components/shared/tag-cloud/tag-cloud.component.scss
+++ b/src/app/components/shared/tag-cloud/tag-cloud.component.scss
@@ -1,3 +1,6 @@
+$header-size: 67px;
+$margin: 15px;
+
 .mat-drawer.mat-drawer-push {
   background-color: var(--background);
 }
@@ -7,16 +10,16 @@ mat-drawer {
 }
 
 ars-fill {
-  width: calc(100% - 30px);
-  height: calc(100% - 30px);
-  margin: 15px;
+  width: calc(100% - #{2 * $margin});
+  height: calc(100% - #{2 * $margin});
+  margin: $margin;
 }
 
 mat-drawer-container {
-  height: calc(100% - 67px);
+  height: calc(100% - #{$header-size});
   width: 100%;
   position: fixed;
-  margin-top: 67px;
+  margin-top: $header-size;
 }
 
 mat-drawer-content {
@@ -31,3 +34,12 @@ mat-drawer-content {
 ::ng-deep mat-progress-spinner circle {
   stroke: var(--on-background) !important;
 }
+
+app-tag-cloud-pop-up {
+  width: max-content;
+  height: max-content;
+}
+
+::ng-deep .spacyTagCloud span {
+  user-select: none !important;
+}
diff --git a/src/app/components/shared/tag-cloud/tag-cloud.component.ts b/src/app/components/shared/tag-cloud/tag-cloud.component.ts
index 74565ecbcdfea3530e546eb9ffd9743ec8139562..451e5bb905137f85500b9aa7824f08e4aef690dc 100644
--- a/src/app/components/shared/tag-cloud/tag-cloud.component.ts
+++ b/src/app/components/shared/tag-cloud/tag-cloud.component.ts
@@ -23,8 +23,10 @@ import { ThemeService } from '../../../../theme/theme.service';
 import { cloneParameters, CloudParameters, CloudTextStyle, CloudWeightSettings } from './tag-cloud.interface';
 import { TopicCloudAdministrationComponent } from '../_dialogs/topic-cloud-administration/topic-cloud-administration.component';
 import { WsCommentServiceService } from '../../../services/websockets/ws-comment-service.service';
-import { TagCloudDataManager } from './tag-cloud.data-manager';
 import { CreateCommentWrapper } from '../../../utils/CreateCommentWrapper';
+import { TopicCloudAdminService } from '../../../services/util/topic-cloud-admin.service';
+import { TagCloudPopUpComponent } from './tag-cloud-pop-up/tag-cloud-pop-up.component';
+import { TagCloudDataService, TagCloudDataTagEntry } from '../../../services/util/tag-cloud-data.service';
 
 class CustomPosition implements Position {
   left: number;
@@ -34,23 +36,26 @@ class CustomPosition implements Position {
               public relativeTop: number) {
   }
 
-  updatePosition(width: number, height: number, text: string, style: CSSStyleDeclaration) {
-    const offsetY = parseFloat(style.height) / 2;
-    const offsetX = parseFloat(style.width) / 2;
+  updatePosition(width: number, height: number, metrics: TextMetrics) {
+    const offsetY = (metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent) / 2;
+    const offsetX = metrics.width / 2;
     this.left = width * this.relativeLeft - offsetX;
     this.top = height * this.relativeTop - offsetY;
   }
 }
 
 class TagComment implements CloudData {
-  constructor(public color: string,
-              public external: boolean,
-              public link: string,
-              public position: Position,
+
+  constructor(public text: string,
               public rotate: number,
-              public text: string,
-              public tooltip: string,
-              public weight: number) {
+              public weight: number,
+              public tagData: TagCloudDataTagEntry,
+              public index: number,
+              public color: string = null,
+              public external: boolean = false,
+              public link: string = null,
+              public position: Position = null,
+              public tooltip: string = null) {
   }
 }
 
@@ -129,6 +134,7 @@ const getDefaultCloudParameters = (): CloudParameters => {
 export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy {
 
   @ViewChild(TCloudComponent, {static: false}) child: TCloudComponent;
+  @ViewChild(TagCloudPopUpComponent) popup: TagCloudPopUpComponent;
   @Input() user: User;
   @Input() roomId: string;
   room: Room;
@@ -157,10 +163,12 @@ export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy {
   //Subscriptions
   headerInterface = null;
   themeSubscription = null;
-  readonly dataManager: TagCloudDataManager;
   private _currentSettings: CloudParameters;
   private _createCommentWrapper: CreateCommentWrapper = null;
   private _subscriptionCommentlist = null;
+  private _calcCanvas: HTMLCanvasElement = null;
+  private _calcRenderContext: CanvasRenderingContext2D = null;
+  private _calcFont: string = null;
 
   constructor(private commentService: CommentService,
               private langService: LanguageService,
@@ -173,13 +181,16 @@ export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy {
               protected roomService: RoomService,
               private themeService: ThemeService,
               private wsCommentService: WsCommentServiceService,
-              private router: Router) {
+              private topicCloudAdmin: TopicCloudAdminService,
+              private router: Router,
+              public dataManager: TagCloudDataService) {
     this.roomId = localStorage.getItem('roomId');
     this.langService.langEmitter.subscribe(lang => {
       this.translateService.use(lang);
     });
-    this.dataManager = new TagCloudDataManager(wsCommentService, commentService);
     this._currentSettings = TagCloudComponent.getCurrentCloudParameters();
+    this._calcCanvas = document.createElement('canvas');
+    this._calcRenderContext = this._calcCanvas.getContext('2d');
   }
 
   private static getCurrentCloudParameters(): CloudParameters {
@@ -207,7 +218,10 @@ export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy {
       } else if (e === 'topicCloudAdministration') {
         this.dialog.open(TopicCloudAdministrationComponent, {
           minWidth: '50%',
-          maxHeight: '80%'
+          maxHeight: '80%',
+          data: {
+            user: this.user
+          }
         });
       }
     });
@@ -251,25 +265,20 @@ export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy {
 
   ngAfterViewInit() {
     document.getElementById('footer_rescale').style.display = 'none';
+    this._calcFont = window.getComputedStyle(document.getElementById('tagCloudComponent')).fontFamily;
+    this.dataManager.bindToRoom(this.roomId);
+    this.dataManager.updateDemoData(this.translateService);
+    this.setCloudParameters(TagCloudComponent.getCurrentCloudParameters(), false);
   }
 
   ngOnDestroy() {
     document.getElementById('footer_rescale').style.display = 'block';
     this.headerInterface.unsubscribe();
     this.themeSubscription.unsubscribe();
-    this.dataManager.deactivate();
-  }
-
-  initTagCloud() {
-    this.dataManager.activate(this.roomId);
-    this.dataManager.updateDemoData(this.translateService);
-    this.setCloudParameters(TagCloudComponent.getCurrentCloudParameters(), false);
-    setTimeout(() => {
-      this.redraw();
-    });
+    this.dataManager.unbindRoom();
   }
 
-  get tagCloudDataManager(): TagCloudDataManager {
+  get tagCloudDataManager(): TagCloudDataService {
     return this.dataManager;
   }
 
@@ -325,7 +334,7 @@ export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy {
           if (rotation === null || this._currentSettings.randomAngles) {
             rotation = Math.floor(Math.random() * 30 - 15);
           }
-          newElements.push(new TagComment(null, true, null, null, rotation, tag, 'TODO', tagData.weight));
+          newElements.push(new TagComment(tag, rotation, tagData.weight, tagData, newElements.length));
         }
       }
     }
@@ -339,6 +348,7 @@ export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy {
           newElements[i].position = new CustomPosition((k + 1) / (size + 1), (line + 1) / (lines + 1));
         }
       }
+      this.updateAlphabeticalPosition(newElements);
     }
     this.data = newElements;
     setTimeout(() => {
@@ -349,31 +359,23 @@ export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy {
   updateTagCloud(dataUpdated = false) {
     this.isLoading = true;
     if (this._currentSettings.sortAlphabetically && this.data.length) {
-      if (dataUpdated || !this.child.cloudDataHtmlElements || !this.child.cloudDataHtmlElements.length) {
-        this.child.reDraw();
-      }
-      const width = this.child.calculatedWidth;
-      const height = this.child.calculatedHeight;
-      this.data.forEach((e, i) => {
-        (e.position as CustomPosition).updatePosition(width, height, e.text,
-          window.getComputedStyle(this.child.cloudDataHtmlElements[i]));
-      });
+      this.updateAlphabeticalPosition(this.data);
     }
     const debounceTime = 1_000;
     const current = new Date().getTime();
     const diff = current - this.lastDebounceTime;
     if (diff >= debounceTime) {
-      this.redraw();
+      this.redraw(dataUpdated);
     } else {
       clearTimeout(this.debounceTimer);
       this.debounceTimer = setTimeout(() => {
-        this.redraw();
+        this.redraw(dataUpdated);
       }, debounceTime - diff);
     }
   }
 
   openTags(tag: CloudData): void {
-    if(this._subscriptionCommentlist !== null){
+    if (this._subscriptionCommentlist !== null) {
       return;
     }
     this._subscriptionCommentlist = this.eventService.on('commentListCreated').subscribe(() => {
@@ -384,17 +386,42 @@ export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy {
     this.router.navigate(['../'], {relativeTo: this.route});
   }
 
-  private redraw(): void {
+  private updateAlphabeticalPosition(elements: TagComment[]): void {
+    const sizes = new Array(10);
+    const fontRange = (this._currentSettings.fontSizeMax - this._currentSettings.fontSizeMin) / 10;
+    for (let i = 1; i <= 10; i++) {
+      sizes[i - 1] = (this._currentSettings.fontSizeMin + fontRange * i).toFixed(0) + '%';
+    }
+    const width = this.child.calculatedWidth;
+    const height = this.child.calculatedHeight;
+    elements.forEach((e, i) => {
+      this._calcRenderContext.font = sizes[e.tagData.adjustedWeight] + ' ' + this._calcFont;
+      (e.position as CustomPosition).updatePosition(width, height, this._calcRenderContext.measureText(e.text));
+    });
+  }
+
+  private redraw(dataUpdate: boolean): void {
     if (this.child === undefined) {
       return;
     }
     this.lastDebounceTime = new Date().getTime();
-    this.child.reDraw();
     this.isLoading = false;
+    if (!dataUpdate) {
+      this.child.reDraw();
+    }
     // This should fix the hover bug (scale was not turned off sometimes)
-    this.child.cloudDataHtmlElements.forEach(elem => {
+    if (this.dataManager.currentData === null) {
+      return;
+    }
+    this.child.cloudDataHtmlElements.forEach((elem, i) => {
+      const dataElement = this.data[i];
       elem.addEventListener('mouseleave', () => {
         elem.style.transform = elem.style.transform.replace(transformationScaleKiller, '').trim();
+        this.popup.leave();
+      });
+      elem.addEventListener('mouseenter', () => {
+        this.popup.enter(elem, dataElement.text, dataElement.tagData,
+          (this._currentSettings.hoverTime + this._currentSettings.hoverDelay) * 1_000);
       });
     });
   }
@@ -425,7 +452,8 @@ export class TagCloudComponent implements OnInit, AfterViewInit, OnDestroy {
         (this._currentSettings.fontSizeMin + fontRange * i).toFixed(0) + '%; }', rules.length);
     }
     customTagCloudStyles.sheet.insertRule('.spacyTagCloud > span:hover, .spacyTagCloud > span:hover > a { color: ' +
-      this._currentSettings.fontColor + '; }', rules.length);
+      this._currentSettings.fontColor + '; background-color: ' +
+      this._currentSettings.backgroundColor + '; }', rules.length);
     customTagCloudStyles.sheet.insertRule('.spacyTagCloudContainer { background-color: ' +
       this._currentSettings.backgroundColor + '; }', rules.length);
   }
diff --git a/src/app/models/comment.ts b/src/app/models/comment.ts
index 2c00741a2d8fcf731699596822cea5716a524ebc..c857181c08a6730ac351acfd3c5ecbce31b83d52 100644
--- a/src/app/models/comment.ts
+++ b/src/app/models/comment.ts
@@ -1,5 +1,4 @@
 import { CorrectWrong } from './correct-wrong.enum';
-import { ViewChild } from '@angular/core';
 
 export class Comment {
   id: string;
@@ -22,6 +21,8 @@ export class Comment {
   number: number;
   keywordsFromQuestioner: string[];
   keywordsFromSpacy: string[];
+  upvotes: number;
+  downvotes: number;
 
   constructor(roomId: string = '',
               creatorId: string = '',
@@ -39,7 +40,9 @@ export class Comment {
               answer: string = '',
               userNumber: number = 0,
               keywordsFromQuestioner: string[] = [],
-              keywordsFromSpacy: string[] = []) {
+              keywordsFromSpacy: string[] = [],
+              upvotes = 0,
+              downvotes = 0) {
     this.id = '';
     this.roomId = roomId;
     this.creatorId = creatorId;
@@ -59,5 +62,7 @@ export class Comment {
     this.userNumber = userNumber;
     this.keywordsFromQuestioner = keywordsFromQuestioner;
     this.keywordsFromSpacy = keywordsFromSpacy;
+    this.upvotes = upvotes;
+    this.downvotes = downvotes;
   }
 }
diff --git a/src/app/services/http/comment.service.ts b/src/app/services/http/comment.service.ts
index a282312f83c9ea33d4fd7dda7c28a0db004a71f4..a5a1a0aeff10c0a40c0323305f19d6fc504c6689 100644
--- a/src/app/services/http/comment.service.ts
+++ b/src/app/services/http/comment.service.ts
@@ -72,7 +72,7 @@ export class CommentService extends BaseHttpService {
   getComment(commentId: string): Observable<Comment> {
     const connectionUrl = `${this.apiUrl.base}${this.apiUrl.comment}/${commentId}`;
     return this.http.get<Comment>(connectionUrl, httpOptions).pipe(
-      map(comment => this.parseUserNumber(comment)),
+      map(comment => this.parseComment(comment)),
       tap(_ => ''),
       catchError(this.handleError<Comment>('getComment'))
     );
@@ -87,9 +87,9 @@ export class CommentService extends BaseHttpService {
         keywordsFromSpacy: JSON.stringify(comment.keywordsFromSpacy),
         keywordsFromQuestioner: JSON.stringify(comment.keywordsFromQuestioner)
       }, httpOptions).pipe(
-        tap(_ => ''),
-        catchError(this.handleError<Comment>('addComment'))
-      );
+      tap(_ => ''),
+      catchError(this.handleError<Comment>('addComment'))
+    );
   }
 
   deleteComment(commentId: string): Observable<Comment> {
@@ -110,16 +110,7 @@ export class CommentService extends BaseHttpService {
       properties: { roomId: roomId, ack: true },
       externalFilters: {}
     }, httpOptions).pipe(
-      map(commentList => commentList.map(comment => {
-          const newComment = this.parseUserNumber(comment);
-          newComment.keywordsFromQuestioner =
-            // @ts-ignore
-            newComment.keywordsFromQuestioner ? JSON.parse(newComment.keywordsFromQuestioner as string) : null;
-          newComment.keywordsFromSpacy =
-            // @ts-ignore
-            newComment.keywordsFromSpacy ? JSON.parse(newComment.keywordsFromSpacy as string) : null;
-          return newComment;
-        })),
+      map(commentList => commentList.map(comment => this.parseComment(comment))),
       tap(_ => ''),
       catchError(this.handleError<Comment[]>('getComments', []))
     );
@@ -132,7 +123,7 @@ export class CommentService extends BaseHttpService {
       externalFilters: {}
     }, httpOptions).pipe(
       map(commentList => {
-        return commentList.map(comment => this.parseUserNumber(comment));
+        return commentList.map(comment => this.parseComment(comment));
       }),
       tap(_ => ''),
       catchError(this.handleError<Comment[]>('getComments', []))
@@ -146,7 +137,7 @@ export class CommentService extends BaseHttpService {
       externalFilters: {}
     }, httpOptions).pipe(
       map(commentList => {
-        return commentList.map(comment => this.parseUserNumber(comment));
+        return commentList.map(comment => this.parseComment(comment));
       }),
       tap(_ => ''),
       catchError(this.handleError<Comment[]>('getComments', []))
@@ -233,8 +224,11 @@ export class CommentService extends BaseHttpService {
   }
 
 
-  parseUserNumber(comment: Comment): Comment {
+  parseComment(comment: Comment): Comment {
     comment.userNumber = this.hashCode(comment.creatorId);
+    // make list out of string "array"
+    comment.keywordsFromQuestioner = comment.keywordsFromQuestioner ? JSON.parse(comment.keywordsFromQuestioner as unknown as string) : null;
+    comment.keywordsFromSpacy = comment.keywordsFromSpacy ? JSON.parse(comment.keywordsFromSpacy as unknown as string) : null;
     return comment;
   }
 
diff --git a/src/app/services/util/tag-cloud-data.service.spec.ts b/src/app/services/util/tag-cloud-data.service.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ee5ff3e99990ea480d89a1d867eb3f7d1236aa89
--- /dev/null
+++ b/src/app/services/util/tag-cloud-data.service.spec.ts
@@ -0,0 +1,16 @@
+/*import { TestBed } from '@angular/core/testing';
+
+import { TagCloudDataService } from './tag-cloud-data.service';
+
+describe('TagCloudDataService', () => {
+  let service: TagCloudDataService;
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({});
+    service = TestBed.inject(TagCloudDataService);
+  });
+
+  it('should be created', () => {
+    expect(service).toBeTruthy();
+  });
+});*/
diff --git a/src/app/components/shared/tag-cloud/tag-cloud.data-manager.ts b/src/app/services/util/tag-cloud-data.service.ts
similarity index 58%
rename from src/app/components/shared/tag-cloud/tag-cloud.data-manager.ts
rename to src/app/services/util/tag-cloud-data.service.ts
index 2c870737338f2f0263899efac73ffdd491369db3..48e81f9512fb43f10f050551c026c574dc538509 100644
--- a/src/app/components/shared/tag-cloud/tag-cloud.data-manager.ts
+++ b/src/app/services/util/tag-cloud-data.service.ts
@@ -1,14 +1,25 @@
-import { Comment } from '../../../models/comment';
-import { Observable, Subject } from 'rxjs';
-import { WsCommentServiceService } from '../../../services/websockets/ws-comment-service.service';
-import { CommentService } from '../../../services/http/comment.service';
-import { CloudParameters } from './tag-cloud.interface';
+import { Injectable } from '@angular/core';
+import { TopicCloudAdminData } from '../../components/shared/_dialogs/topic-cloud-administration/TopicCloudAdminData';
+import { Observable, Subject, Subscription } from 'rxjs';
+import { WsCommentServiceService } from '../websockets/ws-comment-service.service';
+import { CommentService } from '../http/comment.service';
+import { TopicCloudAdminService } from './topic-cloud-admin.service';
+import { CommentFilterOptions } from '../../utils/filter-options';
 import { TranslateService } from '@ngx-translate/core';
+import { CloudParameters } from '../../components/shared/tag-cloud/tag-cloud.interface';
+import { Comment } from '../../models/comment';
+import { CommentFilterUtils } from '../../utils/filter-comments';
+import { Message } from '@stomp/stompjs';
 
 export interface TagCloudDataTagEntry {
   weight: number;
   adjustedWeight: number;
   cachedVoteCount: number;
+  cachedUpVotes: number;
+  cachedDownVotes: number;
+  distinctUsers: Set<number>;
+  firstTimeStamp: Date;
+  categories: Set<string>;
   comments: Comment[];
 }
 
@@ -40,8 +51,8 @@ export type TagCloudMetaDataCount = [
 ];
 
 export enum TagCloudDataSupplyType {
-  fullText,
   keywords,
+  fullText,
   keywordsAndFullText
 }
 
@@ -51,7 +62,10 @@ export enum TagCloudCalcWeightType {
   byLengthAndVotes
 }
 
-export class TagCloudDataManager {
+@Injectable({
+  providedIn: 'root'
+})
+export class TagCloudDataService {
   private _isDemoActive: boolean;
   private _isAlphabeticallySorted: boolean;
   private _dataBus: Subject<TagCloudData>;
@@ -66,9 +80,14 @@ export class TagCloudDataManager {
   private _lastMetaData: TagCloudMetaData = null;
   private readonly _currentMetaData: TagCloudMetaData;
   private _demoData: TagCloudData = null;
+  private _adminData: TopicCloudAdminData = null;
+  private _currentBlacklist: string[] = [];
+  private _subscriptionAdminData: Subscription;
+  private _subscriptionBlacklist: Subscription;
 
   constructor(private _wsCommentService: WsCommentServiceService,
-              private _commentService: CommentService) {
+              private _commentService: CommentService,
+              private _tagCloudAdmin: TopicCloudAdminService) {
     this._isDemoActive = false;
     this._isAlphabeticallySorted = false;
     this._dataBus = new Subject<TagCloudData>();
@@ -88,25 +107,32 @@ export class TagCloudDataManager {
     });
   }
 
-  activate(roomId: string): void {
-    if (this._wsCommentSubscription !== null) {
-      console.error('Tag cloud data manager was already activated!');
-      return;
-    }
+  bindToRoom(roomId: string): void {
     this._roomId = roomId;
-    this.onUpdateData();
-    //TODO Optimize for special events => better performance
-    this._wsCommentSubscription = this._wsCommentService
-      .getCommentStream(this._roomId).subscribe(e => this.onUpdateData());
+    this._subscriptionAdminData = this._tagCloudAdmin.getAdminData.subscribe(adminData => {
+      this._adminData = adminData;
+      this._calcWeightType = this._adminData.considerVotes ? TagCloudCalcWeightType.byLengthAndVotes : TagCloudCalcWeightType.byLength;
+      this._supplyType = this._adminData.keywordORfulltext as unknown as TagCloudDataSupplyType;
+      this.rebuildTagData();
+    });
+    this._subscriptionBlacklist = this._tagCloudAdmin.getBlacklist().subscribe(blacklist => {
+      this._currentBlacklist = blacklist || [];
+      this.rebuildTagData();
+    });
+    this.fetchData();
+    if (!CommentFilterOptions.readFilter().paused) {
+      this._wsCommentSubscription = this._wsCommentService
+        .getCommentStream(this._roomId).subscribe(e => this.onMessage(e));
+    }
   }
 
-  deactivate(): void {
-    if (this._wsCommentSubscription === null) {
-      console.error('Tag cloud data manager was already deactivated!');
-      return;
+  unbindRoom(): void {
+    this._subscriptionBlacklist.unsubscribe();
+    this._subscriptionAdminData.unsubscribe();
+    if (this._wsCommentSubscription !== null) {
+      this._wsCommentSubscription.unsubscribe();
+      this._wsCommentSubscription = null;
     }
-    this._wsCommentSubscription.unsubscribe();
-    this._wsCommentSubscription = null;
   }
 
   updateDemoData(translate: TranslateService): void {
@@ -115,9 +141,14 @@ export class TagCloudDataManager {
       for (let i = 10; i >= 1; i--) {
         this._demoData.set(text.replace('%d', '' + i), {
           cachedVoteCount: 0,
+          cachedUpVotes: 0,
+          cachedDownVotes: 0,
           comments: [],
           weight: i,
-          adjustedWeight: i - 1
+          adjustedWeight: i - 1,
+          categories: new Set<string>(),
+          distinctUsers: new Set<number>(),
+          firstTimeStamp: new Date()
         });
       }
     });
@@ -182,6 +213,11 @@ export class TagCloudDataManager {
     return this._isAlphabeticallySorted;
   }
 
+  blockWord(tag: string): void {
+    this._tagCloudAdmin.addWordToBlacklist(tag.toLowerCase());
+    this.rebuildTagData();
+  }
+
   updateConfig(parameters: CloudParameters): boolean {
     if (parameters.sortAlphabetically !== this._isAlphabeticallySorted) {
       this._isAlphabeticallySorted = parameters.sortAlphabetically;
@@ -223,7 +259,7 @@ export class TagCloudDataManager {
     return this._lastFetchedData;
   }
 
-  private onUpdateData(): void {
+  private fetchData(): void {
     this._commentService.getFilteredComments(this._roomId).subscribe((comments: Comment[]) => {
       this._lastFetchedComments = comments;
       if (this._isDemoActive) {
@@ -247,6 +283,9 @@ export class TagCloudDataManager {
   }
 
   private rebuildTagData() {
+    if (!this._lastFetchedComments) {
+      return;
+    }
     const currentMeta = this._isDemoActive ? this._lastMetaData : this._currentMetaData;
     const data: TagCloudData = new Map<string, TagCloudDataTagEntry>();
     const users = new Set<number>();
@@ -263,13 +302,43 @@ export class TagCloudDataManager {
         keywords = [];
       }
       for (const keyword of keywords) {
-        //TODO Check spelling & check profanity
+        const lowerCaseKeyWord = keyword.toLowerCase();
+        let profanity = false;
+        for (const word of this._currentBlacklist) {
+          if (lowerCaseKeyWord.includes(word)) {
+            profanity = true;
+            break;
+          }
+        }
+        if (profanity) {
+          continue;
+        }
         let current = data.get(keyword);
         if (current === undefined) {
-          current = {cachedVoteCount: 0, comments: [], weight: 0, adjustedWeight: 0};
+          current = {
+            cachedVoteCount: 0,
+            cachedUpVotes: 0,
+            cachedDownVotes: 0,
+            comments: [],
+            weight: 0,
+            adjustedWeight: 0,
+            distinctUsers: new Set<number>(),
+            categories: new Set<string>(),
+            firstTimeStamp: comment.timestamp
+          };
           data.set(keyword, current);
         }
         current.cachedVoteCount += comment.score;
+        current.cachedUpVotes += comment.upvotes;
+        current.cachedDownVotes += comment.downvotes;
+        current.distinctUsers.add(comment.userNumber);
+        if (comment.tag) {
+          current.categories.add(comment.tag);
+        }
+        // @ts-ignore
+        if (current.firstTimeStamp - comment.timestamp > 0) {
+          current.firstTimeStamp = comment.timestamp;
+        }
         current.comments.push(comment);
       }
       users.add(comment.userNumber);
@@ -300,4 +369,60 @@ export class TagCloudDataManager {
     }
   }
 
+  private onMessage(message: Message): void {
+    const msg = JSON.parse(message.body);
+    const payload = msg.payload;
+    switch (msg.type) {
+      case 'CommentCreated':
+        this._commentService.getComment(payload.id).subscribe(c => {
+          if (CommentFilterUtils.checkComment(c)) {
+            this._lastFetchedComments.push(c);
+            this.rebuildTagData();
+          }
+        });
+        break;
+      case 'CommentPatched':
+        for (const comment of this._lastFetchedComments) {
+          if (payload.id === comment.id) {
+            let needRebuild = false;
+            for (const [key, value] of Object.entries(payload.changes)) {
+              switch (key) {
+                case 'score':
+                  comment.score = value as number;
+                  needRebuild = true;
+                  break;
+                case 'upvotes':
+                  comment.upvotes = value as number;
+                  needRebuild = true;
+                  break;
+                case 'downvotes':
+                  comment.downvotes = value as number;
+                  needRebuild = true;
+                  break;
+                case 'ack':
+                  const isNowAck = value as boolean;
+                  if (!isNowAck) {
+                    this._lastFetchedComments = this._lastFetchedComments.filter((el) => el.id !== payload.id);
+                  }
+                  needRebuild = true;
+                  break;
+                case 'tag':
+                  comment.tag = value as string;
+                  needRebuild = true;
+                  break;
+              }
+            }
+            if (needRebuild) {
+              this.rebuildTagData();
+            }
+            break;
+          }
+        }
+        break;
+      case 'CommentDeleted':
+        this._lastFetchedComments = this._lastFetchedComments.filter((el) => el.id !== payload.id);
+        this.rebuildTagData();
+        break;
+    }
+  }
 }
diff --git a/src/app/services/util/topic-cloud-admin.service.ts b/src/app/services/util/topic-cloud-admin.service.ts
index fdecfe2f88e79ef25650ac1890c6028c3dfa424e..683739b340167b4360bdbfd2fb9e23fd0918cd67 100644
--- a/src/app/services/util/topic-cloud-admin.service.ts
+++ b/src/app/services/util/topic-cloud-admin.service.ts
@@ -162,7 +162,7 @@ export class TopicCloudAdminService {
   }
 
   getDefaultSpacyTagsDE(): string[] {
-    let tags: string[];
+    const tags: string[] = [];
     spacyLabels.de.forEach(label => {
       tags.push(label.tag);
     });
@@ -170,7 +170,7 @@ export class TopicCloudAdminService {
   }
 
   getDefaultSpacyTagsEN(): string[] {
-    let tags: string[];
+    const tags: string[] = [];
     spacyLabels.en.forEach(label => {
       tags.push(label.tag);
     });
diff --git a/src/assets/i18n/participant/de.json b/src/assets/i18n/participant/de.json
index c22c7c8276b6f5416f6ab3cdb221d6a2aac2a291..b431fe33f527cac0d72a4f28f87d4e344aad5461 100644
--- a/src/assets/i18n/participant/de.json
+++ b/src/assets/i18n/participant/de.json
@@ -87,7 +87,7 @@
     "cancel": "Abbrechen",
     "delete": "Löschen"
   },
-  "spacy-dialog":{
+  "spacy-dialog": {
     "auto": "auto",
     "de": "Deutsch",
     "en": "Englisch",
@@ -155,7 +155,6 @@
     "sure": "Bist du sicher?",
     "grammar-check": "Rechtschreibprüfung"
   },
-
   "home-page": {
     "exactly-8": "Ein Raum-Code hat genau 8 Ziffern.",
     "no-room-found": "Es wurde keine Sitzung mit diesem Raum-Code gefunden.",
@@ -218,7 +217,7 @@
     "filter-lbl": "Filter-Menü anzeigen",
     "filter-lbl-favorites": "Favorisierte Fragen filtern",
     "filter-lbl-approved": "Bejahte Fragen filtern",
-    "filter-lbl-disapproved":"Verneinte Fragen filtern",
+    "filter-lbl-disapproved": "Verneinte Fragen filtern",
     "filter-tags-lbl": "Nach Kategorien filtern",
     "user-lbl": "Benutzer-Menü öffnen",
     "slider-lbl": "Fragen-Zoom einstellen",
@@ -233,7 +232,25 @@
   "tag-cloud": {
     "config": "Wolkenansicht ändern",
     "administration": "Wolkenthemen editieren",
-    "demo-data-topic": "Thema %d"
+    "demo-data-topic": "Thema %d",
+    "overview-question-topic-tooltip": "Anzahl gestellter Fragen mit diesem Thema",
+    "overview-questioners-topic-tooltip": "Anzahl Fragensteller*innen mit diesem Thema",
+    "period-since-first-comment":"Zeitraum seit erstem Kommentar",
+    "upvote-topic": "Upvotes für dieses Thema",
+    "downvote-topic": "Downvotes für dieses Thema",
+    "blacklist-topic": "Thema zur Blacklist hinzufügen"
+  },
+  "tag-cloud-popup": {
+    "few-seconds": "wenige Sekunden",
+    "few-minutes": "wenige Minuten",
+    "some-minutes": "{{minutes}} Minuten",
+    "one-hour": "1 Stunde",
+    "some-hours": "{{hours}} Stunden",
+    "one-day": "1 Tag",
+    "some-days": "{{days}} Tage",
+    "one-week": "1 Woche",
+    "some-weeks": "{{weeks}} Wochen",
+    "some-months": "{{months}} Monate"
   },
   "topic-cloud-dialog": {
     "cancel": "Abbrechen",
@@ -284,24 +301,24 @@
     "read-more": "Mehr lesen",
     "read-less": "Weniger lesen"
   },
-  "tag-cloud-config":{
-    "title":"Tag-Cloud Einstellungen",
-    "general":"Allgemeine Einstellungen",
-    "overflow":"Überlauf",
-    "height":"Höhe",
-    "random-angle":"Zufallswinkel",
-    "realign":"Neu ausrichten",
-    "background":"Hintergrundfarbe",
-    "word-delay":"Wortverzögerung",
-    "hover-color":"Schriftfarbe",
-    "hover-scale":"Hover Skala",
-    "hover-time":"Hover Zeit",
-    "hover-title":"Hover Einstellungen",
-    "hover-delay":"Hover Verzögerung",
-    "cancel-btn":"Abbruch",
-    "save-btn":"Speichern",
-    "font-size-min":"Schriftgröße min",
-    "font-size-max":"Schriftgröße max",
+  "tag-cloud-config": {
+    "title": "Tag-Cloud Einstellungen",
+    "general": "Allgemeine Einstellungen",
+    "overflow": "Überlauf",
+    "height": "Höhe",
+    "random-angle": "Zufallswinkel",
+    "realign": "Neu ausrichten",
+    "background": "Hintergrundfarbe",
+    "word-delay": "Wortverzögerung",
+    "hover-color": "Schriftfarbe",
+    "hover-scale": "Hover Skala",
+    "hover-time": "Hover Zeit",
+    "hover-title": "Hover Einstellungen",
+    "hover-delay": "Hover Verzögerung",
+    "cancel-btn": "Abbruch",
+    "save-btn": "Speichern",
+    "font-size-min": "Schriftgröße min",
+    "font-size-max": "Schriftgröße max",
     "select-color": "Farbauswahl",
     "random-angle-tooltip": "Anordnung der Winkel zufällig generieren",
     "background-tooltip": "Auswahl der Hintergrundfarbe",
diff --git a/src/assets/i18n/participant/en.json b/src/assets/i18n/participant/en.json
index 07df33bb3518eb767082a48c8e43a716f01e4b61..9821382bbad0a6ff070a20aa026309e4a3913983 100644
--- a/src/assets/i18n/participant/en.json
+++ b/src/assets/i18n/participant/en.json
@@ -238,7 +238,25 @@
   "tag-cloud": {
     "config": "Modify cloud view",
     "administration": "Edit cloud topics",
-    "demo-data-topic": "Topic %d"
+    "demo-data-topic": "Topic %d",
+    "overview-question-topic-tooltip": "Number of questions with this topic",
+    "overview-questioners-topic-tooltip": "Number of questioners with this topic",
+    "period-since-first-comment":"Period since first comment",
+    "upvote-topic": "Upvotes for this topic",
+    "downvote-topic": "Downvotes for this topic",
+    "blacklist-topic": "Add topic to blacklist"
+  },
+  "tag-cloud-popup": {
+    "few-seconds": "few seconds",
+    "few-minutes": "few minutes",
+    "some-minutes": "{{minutes}} minutes",
+    "one-hour": "1 hour",
+    "some-hours": "{{hours}} hours",
+    "one-day": "1 day",
+    "some-days": "{{days}} days",
+    "one-week": "1 week",
+    "some-weeks": "{{weeks}} weeks",
+    "some-months": "{{months}} months"
   },
   "topic-cloud-dialog":{
     "edit": "Edit",