Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…NPS score visualizer
  • Loading branch information
tsv2013 committed Jun 20, 2024
1 parent 793d2e3 commit 4766779
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 1 deletion.
7 changes: 6 additions & 1 deletion src/analytics-localization/english.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,12 @@ export var englishStrings = {
answer: "Answer",
correctAnswer: "Correct answer: ",
percent: "Percent",
responses: "Responses"
responses: "Responses",
visualizer_nps: "NPS Score",
npsScore: "NPS Score",
npsPromoters: "Promoters",
npsPassives: "Passives",
npsDetractors: "Detractors",
};

// Uncomment the lines below if you create a custom dictionary.
Expand Down
1 change: 1 addition & 0 deletions src/entries/summary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,6 @@ export * from "../wordcloud/wordcloud";
export * from "../wordcloud/stopwords/index";
export * from "../text";
export * from "../choices-table";
export * from "../nps";

export { DocumentHelper } from "../utils/index";
33 changes: 33 additions & 0 deletions src/nps.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
.sa-visualizer-nps {
display: flex;
flex-direction: row;
gap: 16px;
}

.sa-visualizer-nps__score-part {
display: flex;
flex-direction: column;
padding: 0 16px;
}

.sa-visualizer-nps__score-part-title {
font-size: 32px;
text-align: center;
color: #909090;
}

.sa-visualizer-nps__score-part-values {
display: flex;
flex-direction: row;
gap: 16px;
align-items: baseline;
}

.sa-visualizer-nps__score-part-value {
font-size: 48px;
}

.sa-visualizer-nps__score-part-percent {
font-size: 24px;
color: #606060;
}
127 changes: 127 additions & 0 deletions src/nps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { Question, Event } from "survey-core";
import { VisualizerBase } from "./visualizerBase";
import { VisualizationManager } from "./visualizationManager";
import { DocumentHelper } from "./utils";
import { localization } from "./localizationManager";

import "./nps.scss";

export class NpsVizualizerWidget {
private _renderedTarget: HTMLDivElement = undefined;

constructor(private _model: NpsVizualizer, private _data: { detractors: number, passive: number, promoters: number, total: number }) {
}

private renderScorePart(partId: string, value: number, percent?: number) {
const scorePartElement = DocumentHelper.createElement("div", "sa-visualizer-nps__score-part");
const titleElement = DocumentHelper.createElement("div", "sa-visualizer-nps__score-part-title");
titleElement.innerText = localization.getString(partId);
scorePartElement.appendChild(titleElement);
const valuesElement = DocumentHelper.createElement("div", "sa-visualizer-nps__score-part-values");
scorePartElement.appendChild(valuesElement);
const valueElement = DocumentHelper.createElement("div", "sa-visualizer-nps__score-part-value");
valueElement.innerText = "" + value;
valuesElement.appendChild(valueElement);
if(percent) {
const percentElement = DocumentHelper.createElement("div", "sa-visualizer-nps__score-part-percent");
percentElement.innerText = "" + percent + "%";
valuesElement.appendChild(percentElement);
}
return scorePartElement;
}
public render(target: HTMLDivElement): void {
this._renderedTarget = target;
var npsElement = DocumentHelper.createElement("div", "sa-visualizer-nps");
npsElement.appendChild(this.renderScorePart("npsScore", ((this._data.promoters - this._data.detractors) / this._data.total) * 100));
npsElement.appendChild(this.renderScorePart("npsPromoters", this._data.promoters, this._data.promoters / this._data.total * 100));
npsElement.appendChild(this.renderScorePart("npsPassives", this._data.total - this._data.promoters - this._data.detractors, (this._data.total - this._data.promoters - this._data.detractors) / this._data.total * 100));
npsElement.appendChild(this.renderScorePart("npsDetractors", this._data.detractors, this._data.detractors / this._data.total * 100));
target.appendChild(npsElement);
}
public dispose(): void {
if(!!this._renderedTarget) {
this._renderedTarget.innerHTML = "";
this._renderedTarget = undefined;
}
}
}

export class NpsAdapter {
private _npsVizualizer: any;

constructor(private model: NpsVizualizer) {}

public get npsVizualizer(): any {
return this._npsVizualizer;
}

public create(element: HTMLElement): any {
const data = this.model.getCalculatedValues();
this._npsVizualizer = new NpsVizualizerWidget(this.model, data);
this._npsVizualizer.render(element);
return this._npsVizualizer;
}

public destroy(node: HTMLElement): void {
if(this._npsVizualizer && typeof this._npsVizualizer.dispose === "function") {
this._npsVizualizer.dispose();
}
this._npsVizualizer = undefined;
}
}
export class NpsVizualizer extends VisualizerBase {
private _npsAdapter: NpsAdapter;

constructor(
question: Question,
data: Array<{ [index: string]: any }>,
options?: Object,
name?: string
) {
super(question, data, options, name || "nps");
this._npsAdapter = new NpsAdapter(this);
}

public getCalculatedValues(): any {
let result = {
detractors: 0,
passive: 0,
promoters: 0,
total: 0,
};

this.data.forEach((row) => {
const rowValue: any = row[this.question.name];
const scoreValue = parseInt(rowValue);
if (!Number.isNaN(scoreValue)) {
if(scoreValue <= 6) {
result.detractors++;
} else if(scoreValue >= 9) {
result.promoters++;
} else {
result.passive++;
}
result.total++;
}
});

return result;
}

protected destroyContent(container: HTMLElement) {
this._npsAdapter.destroy(container);
super.destroyContent(container);
}

protected renderContent(container: HTMLElement) {
this._npsAdapter.create(container);
this.afterRender(this.contentContainer);
}

destroy() {
this._npsAdapter.destroy(this.contentContainer);
super.destroy();
}
}

// VisualizationManager.registerVisualizer("rating", NpsVizualizer);

0 comments on commit 4766779

Please sign in to comment.