Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added KPI visualization #4640

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 60 additions & 9 deletions client/app/components/dashboards/dashboard-grid.less
Original file line number Diff line number Diff line change
Expand Up @@ -113,15 +113,66 @@
overflow: hidden;
}

.counter-visualization-content {
position: absolute;
left: 10px;
top: 15px;
right: 10px;
bottom: 15px;
height: auto;
overflow: hidden;
padding: 0;
.counter-visualization-container {
height: 100%;

.counter-visualization-content {
position: absolute;
left: 10px;
top: 15px;
right: 10px;
bottom: 15px;
height: auto;
overflow: hidden;
padding: 0;
}
}

.kpi-visualization-container {
height: 100%;

.kpi-visualization-content {
position: absolute;
left: 10px;
top: 15px;
right: 10px;
bottom: 15px;
height: auto;
overflow: hidden;
padding: 0;
}
}
}

.query-fixed-layout {
.visualization-renderer > .visualization-renderer-wrapper {
.counter-visualization-container {
// counter is too large on Query pages, so let's add some constraints
max-width: 600px;
max-height: 400px;
// center it
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
margin: auto;
}
}

.visualization-renderer > .visualization-renderer-wrapper {
.kpi-visualization-container {
// kpi widget is too large on Query pages, so let's add some constraints
max-width: 600px;
max-height: 400px;
// center it
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
margin: auto;
}
}
}

Expand Down
67 changes: 67 additions & 0 deletions viz-lib/src/visualizations/kpi/Editor/FormatSettings.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from "react";
import {Section, Input, InputNumber, Switch, ContextHelp} from "@/components/visualizations/editor";
import { EditorPropTypes } from "@/visualizations";

import { isValueNumber } from "../utils";

export default function FormatSettings({ options, data, onOptionsChange }) {
const inputsEnabled = isValueNumber(data.rows, options);
return (
<React.Fragment>
<Section>
<Input
layout="horizontal"
label={
<React.Fragment>
Values Format
<ContextHelp.NumberFormatSpecs />
</React.Fragment>
}
defaultValue={options.valuesFormat}
onChange={e => onOptionsChange({ valuesFormat: e.target.value })}
/>
</Section>

<Section>
<Input
layout="horizontal"
label={
<React.Fragment>
Percent Format
<ContextHelp.NumberFormatSpecs />
</React.Fragment>
}
defaultValue={options.percentFormat}
onChange={e => onOptionsChange({ percentFormat: e.target.value })}
/>
</Section>

<Section>
<Switch
defaultChecked={options.showDeltaPercentage}
onChange={showDeltaPercentage => onOptionsChange({ showDeltaPercentage })}>
Show delta as percentage
</Switch>
</Section>

<Section>
<Switch
defaultChecked={options.showDeltaAmount}
onChange={showDeltaAmount => onOptionsChange({ showDeltaAmount })}>
Show delta as amount
</Switch>
</Section>

<Section>
<Switch
defaultChecked={options.invertTrendDirection}
onChange={invertTrendDirection => onOptionsChange({ invertTrendDirection })}>
Invert trend direction
</Switch>
</Section>

</React.Fragment>
);
}

FormatSettings.propTypes = EditorPropTypes;
53 changes: 53 additions & 0 deletions viz-lib/src/visualizations/kpi/Editor/GeneralSettings.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { map } from "lodash";
import React from "react";
import { Section, Select, Input, InputNumber, Switch } from "@/components/visualizations/editor";
import { EditorPropTypes } from "@/visualizations";

export default function GeneralSettings({ options, data, visualizationName, onOptionsChange }) {
return (
<React.Fragment>

<Section>
<Select
layout="horizontal"
label="Current Value Column Name"
defaultValue={options.currentValueColName}
onChange={currentValueColName => onOptionsChange({ currentValueColName })}>
{map(data.columns, col => (
<Select.Option key={col.name}>
{col.name}
</Select.Option>
))}
</Select>
</Section>

<Section>
<Select
layout="horizontal"
label="Target Value Column Name"
defaultValue={options.targetValueColName}
onChange={targetValueColName => onOptionsChange({ targetValueColName })}>
<Select.Option value="">No target value</Select.Option>
{map(data.columns, col => (
<Select.Option key={col.name}>
{col.name}
</Select.Option>
))}
</Select>
</Section>

<Section>
<Input
layout="horizontal"
label="Target Value Prefix Label"
placeholder="Current"
defaultValue={options.targetValuePrefixLabel}
onChange={e => onOptionsChange({ targetValuePrefixLabel: e.target.value })}
/>
</Section>

</React.Fragment>
);
}

GeneralSettings.propTypes = EditorPropTypes;
9 changes: 9 additions & 0 deletions viz-lib/src/visualizations/kpi/Editor/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import createTabbedEditor from "@/components/visualizations/editor/createTabbedEditor";

import GeneralSettings from "./GeneralSettings";
import FormatSettings from "./FormatSettings";

export default createTabbedEditor([
{ key: "General", title: "General", component: GeneralSettings },
{ key: "Format", title: "Format", component: FormatSettings },
]);
80 changes: 80 additions & 0 deletions viz-lib/src/visualizations/kpi/Renderer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { isFinite } from "lodash";
import React, { useState, useEffect } from "react";
import cx from "classnames";
import resizeObserver from "@/services/resizeObserver";
import { RendererPropTypes } from "@/visualizations";

import { getKpiData } from "./utils";

import "./render.less";

function getContainerStyles(scale) {
return {
msTransform: `scale(${scale})`,
MozTransform: `scale(${scale})`,
WebkitTransform: `scale(${scale})`,
transform: `scale(${scale})`,
};
}

function getCounterScale(container) {
const inner = container.firstChild;
const scale = Math.min(container.offsetWidth / inner.offsetWidth, container.offsetHeight / inner.offsetHeight);
return Number(isFinite(scale) ? scale : 1).toFixed(2); // keep only two decimal places;
}

export default function Renderer({ data, options, visualizationName }) {
const [scale, setScale] = useState("1.00");
const [container, setContainer] = useState(null);

useEffect(() => {
if (container) {
const unwatch = resizeObserver(container, () => {
setScale(getCounterScale(container));
});
return unwatch;
}
}, [container]);

useEffect(() => {
if (container) {
// update scaling when options or data change (new formatting, values, etc.
// may change inner container dimensions which will not be tracked by `resizeObserver`);
setScale(getCounterScale(container));
}
}, [data, options, container]);

const {
currentValue,
trendDirection,
deltaValue,
targetValuePrefixLabel,
targetValue,
} = getKpiData(data.rows, options);
return (
<div className="kpi-visualization-container">
<div className="kpi-visualization-content" ref={setContainer}>
<div style={getContainerStyles(scale)}>

<div className="kpi-visualization-current-value">
{currentValue}
</div>

{targetValue && (
<div className={cx("kpi-visualization-delta-value", "trend-" + trendDirection)}>
{deltaValue}
</div>
)}

{targetValue && (
<div className="kpi-visualization-target-value">
{targetValuePrefixLabel} ({targetValue})
</div>
)}
</div>
</div>
</div>
);
}

Renderer.propTypes = RendererPropTypes;
20 changes: 20 additions & 0 deletions viz-lib/src/visualizations/kpi/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Renderer from "./Renderer";
import Editor from "./Editor";

const DEFAULT_OPTIONS = {
currentValueColName: "current_value",
targetValueColName: "target_value",
valuesFormat: "0,0[.]00",
percentFormat: "0[.]00%",
};

export default {
type: "KPI",
name: "KPI",
getOptions: options => ({ ...DEFAULT_OPTIONS, ...options }),
Renderer,
Editor,

defaultColumns: 2,
defaultRows: 5,
}
44 changes: 44 additions & 0 deletions viz-lib/src/visualizations/kpi/render.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
.kpi-visualization-container {
display: block;
text-align: center;
padding: 15px 10px;
overflow: hidden;
position: relative;

.kpi-visualization-content {
margin: 0;
padding: 0;
font-size: 80px;
line-height: normal;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;

.kpi-visualization-current-value {
font-size: 1em;
display: block;
margin-bottom: 10px;
}

.kpi-visualization-delta-value, .kpi-visualization-target-value {
font-size: .4em;
line-height: 1.3;
}

.kpi-visualization-delta-value {
&.trend-positive {
color: #5cb85c;
}

&.trend-negative {
color: #d9534f;
}
}

.kpi-visualization-target-value {
color: #ccc;
}
}
}
Loading