mstr-viz
Version:
A new dev tool for creating custom visualizations
281 lines (250 loc) • 10.8 kB
JavaScript
import { adjustProperties } from "./properties/adjustProperties";
import { getDefaultProperties } from "./properties/getDefaultProperties";
import { enhanceHost } from "./utils/enhanceHost";
mstrmojo.requiresDescsWPrefix("<%= pluginName %>.", 10, 11, 12, 13, 14);
// mojo module
mstrmojo.requiresCls("mstrmojo.CustomVisBase", "mstrmojo.customviz.CustomVisEnums");
const { GraphicModel } = mstrmojo.customviz;
const { ENUM_RAW_DATA_FORMAT } = mstrmojo.models.template.DataInterface;
const $CustomVisEnums = mstrmojo.customviz.CustomVisEnums;
const $ENUM_LEGEND_PROPS = $CustomVisEnums.ENUM_LEGEND_PROPS;
mstrmojo.plugins.<%= pluginName %>.<%= pluginName %> = mstrmojo.declare(
mstrmojo.CustomVisBase,
null,
{
scriptClass: "mstrmojo.plugins.<%= pluginName %>.<%= pluginName %>",
cssClass: "<% print(_.toLower(pluginName)) %>",
errorMessage:
"Either there is not enough data to display the visualization or the visualization configuration is incomplete.",
errorDetails:
"This visualization requires one or more attributes and one metric.",
useRichTooltip: false,
reuseDOMNode: false,
draggable: false,
supportNEE: true,
enableLegend: true,
init(props) {
this._super(props);
enhanceHost(this);
const defaultPropertyValues = getDefaultProperties();
let legendProps;
legendProps = defaultPropertyValues[$CustomVisEnums.LEGEND_PROPERTY_SET] = {};
legendProps[$ENUM_LEGEND_PROPS.LEGEND_FONT_FAMILY] = 'Open Sans';
legendProps[$ENUM_LEGEND_PROPS.LEGEND_FONT_WEIGHT] = false;
legendProps[$ENUM_LEGEND_PROPS.LEGEND_FONT_ITALIC] = false;
legendProps[$ENUM_LEGEND_PROPS.LEGEND_FONT_UNDERLINE] = false;
legendProps[$ENUM_LEGEND_PROPS.LEGEND_FONT_LINE_THROUGH] = false;
legendProps[$ENUM_LEGEND_PROPS.LEGEND_FONT_SIZE] = '12pt';
legendProps[$ENUM_LEGEND_PROPS.LEGEND_FONT_COLOR] = '#000000';
legendProps[$ENUM_LEGEND_PROPS.SHOW_LEGEND] = false;
this.setDefaultPropertyValues(defaultPropertyValues);
},
createGraphicModels() {
const host = this;
const dropZoneModel = this.zonesModel;
const dropZoneAttribute = dropZoneModel.getDropZoneObjectsByName('Attribute');
const dropZoneMetric = dropZoneModel.getDropZoneObjectsByName('Metric');
const dropZoneColorBy = dropZoneModel.getDropZoneObjectsByName('Colorby');
const dropZoneSizeBy = dropZoneModel.getDropZoneObjectsByName('Size');
if (!dropZoneAttribute || dropZoneAttribute.length === 0 || !dropZoneMetric || dropZoneMetric.length === 0) {
host.graphicModelsNA = true;
throw new Error();
}
host.hasColorBy = dropZoneColorBy && dropZoneColorBy.length > 0;
host.hasSizeBy = dropZoneSizeBy && dropZoneSizeBy.length > 0;
const rawData = this.dataInterface.getRawData(
ENUM_RAW_DATA_FORMAT.ROWS_ADV,
{
hasSelection: true,
hasTitleName: true,
hasThreshold: true,
additionalAttrIds: this.additionalAttrIds,
colorByInfo: dropZoneModel.getColorByAttributes(),
}
);
host.maxValueSizeBy = -Infinity;
host.minValueSizeBy = Infinity;
return rawData.map((row) => {
const { values, headers } = row;
const graphicModel = new GraphicModel();
//In case of putting the same attribute/metric into the colorBy/sizeBy dropzone as Attribute/Metric, we will get only one header or value set here.
const textColorBy = headers[1] ? headers[1].name : headers[0].name;
const valueSizeBy = values[1] ? values[1].rv : values[0].rv;
graphicModel.idValueMapping = row.idValueMapping;
graphicModel.setCustomProperties({
text: headers[0].name,
textColorBy: textColorBy,
value: values[0].rv,
valueSizeBy: valueSizeBy,
paletteColor: host.getColorBy(row.colorInfo),
});
graphicModel.setId(this.getSelectorHash(row.metricSelector));
graphicModel.setSelector(row.metricSelector, true);
host.maxValueSizeBy = host.maxValueSizeBy < parseFloat(valueSizeBy) ? parseFloat(valueSizeBy) : host.maxValueSizeBy;
host.minValueSizeBy = host.minValueSizeBy > parseFloat(valueSizeBy) ? parseFloat(valueSizeBy) : host.minValueSizeBy;
return graphicModel;
});
},
clearDomNode() {
const canvasNode = this.canvasContainer;
canvasNode.innerHTML = "";
},
plot(appliedProperties = null) {
const host = this;
host.clearDomNode();
const graphicModels = host.getGraphicModels();
if (graphicModels.length === 0) {
return;
}
let properties;
if (appliedProperties) {
properties = adjustProperties(appliedProperties, host);
} else {
const savedProperties = JSON.parse(host.getProperty("unifiedProperty"));
properties = adjustProperties(savedProperties, host);
}
// render part
const {
showAttribute,
showMetric,
attributeBackgroundColor,
metricBackgroundColor,
attributeFontFamily,
attributeFontStyle,
attributeFontColor,
attributeFontSize,
colorByFontFamily,
colorByFontStyle,
colorByFontColor,
colorByFontSize,
metricFontFamily,
metricFontStyle,
metricFontColor,
metricFontSize,
sizeByFontFamily,
sizeByFontStyle,
sizeByFontColor,
sizeByFontSize,
} = properties;
const addStyle = (
element,
fontFamily,
fontColor,
fontSize,
fontStyle
) => {
element.style.fontFamily = fontFamily;
element.style.color = fontColor;
element.style.fontSize = `${fontSize}pt`;
const [isBold, isItalic, isUnderlined, isCrossedOut] = fontStyle;
element.style.fontWeight = isBold ? "bold" : "normal";
element.style.fontStyle = isItalic ? "italic" : "normal";
const textDecorationOptions = [];
if (isUnderlined) textDecorationOptions.push("underline");
if (isCrossedOut) textDecorationOptions.push("line-through");
const textDecoration = textDecorationOptions.join(" ");
element.style.textDecoration = textDecoration;
};
const drawSizeByItem = (sizeByContainer, sizeByItem, value) => {
let percentage = parseFloat(value - host.minValueSizeBy) / (host.maxValueSizeBy - host.minValueSizeBy);
percentage = (percentage + 0.3) / 1.3;
const border = parseInt(getComputedStyle(sizeByContainer, null)['border']);
const padding = parseInt(getComputedStyle(sizeByContainer, null)['padding']);
const height = parseInt(getComputedStyle(sizeByContainer, null)['height']);
const width = parseInt(getComputedStyle(sizeByContainer, null)['width']);
sizeByItem.style.height = height * percentage + 'px';
sizeByItem.style.width = width * percentage + 'px';
sizeByItem.style.top = border + padding + height * (1 - percentage) / 2 + 'px';
}
const gridColCnt = 4 - (host.hasSizeBy ? 0 : 1) - (host.hasColorBy ? 0 : 1);
graphicModels.forEach((graphicModel) => {
const container = document.createElement("div");
container.classList.add("container");
const attributeContainer = document.createElement("div");
attributeContainer.classList.add("attributeContainer");
attributeContainer.style.backgroundColor = attributeBackgroundColor;
attributeContainer.style.width = (100.0 / gridColCnt) + '%';
const attributeContainerText = document.createTextNode(
showAttribute ? graphicModel.text : ""
);
addStyle(
attributeContainer,
attributeFontFamily,
attributeFontColor,
attributeFontSize,
attributeFontStyle
);
attributeContainer.appendChild(attributeContainerText);
let colorByContainer;
if (host.hasColorBy) {
colorByContainer = document.createElement("div");
colorByContainer.classList.add("colorByContainer");
colorByContainer.style.backgroundColor = graphicModel.paletteColor;
colorByContainer.style.width = (100.0 / gridColCnt) + '%';
const colorByContainerText = document.createTextNode(
graphicModel.textColorBy
);
addStyle(
colorByContainer,
colorByFontFamily,
colorByFontColor,
colorByFontSize,
colorByFontStyle,
);
colorByContainer.appendChild(colorByContainerText);
}
const metricContainer = document.createElement("div");
metricContainer.classList.add("metricContainer");
metricContainer.style.backgroundColor = metricBackgroundColor;
metricContainer.style.width = (100.0 / gridColCnt) + '%';
const metricContainerText = document.createTextNode(
showMetric ? graphicModel.value : ""
);
addStyle(
metricContainer,
metricFontFamily,
metricFontColor,
metricFontSize,
metricFontStyle
);
metricContainer.appendChild(metricContainerText);
let sizeByContainer;
let sizeByItem;
if (host.hasSizeBy) {
sizeByContainer = document.createElement("div");
sizeByContainer.classList.add("sizeByContainer");
sizeByContainer.style.width = (100.0 / gridColCnt) + '%';
const sizeByContainerText = document.createTextNode(
showMetric ? graphicModel.valueSizeBy : ""
);
addStyle(
sizeByContainer,
sizeByFontFamily,
sizeByFontColor,
sizeByFontSize,
sizeByFontStyle,
);
sizeByItem = document.createElement("div");
sizeByItem.style.position = 'absolute';
sizeByItem.style.backgroundColor = '#CCCCCC';
sizeByItem.style.zIndex = -1;
sizeByContainer.appendChild(sizeByItem);
sizeByContainer.appendChild(sizeByContainerText);
}
container.appendChild(attributeContainer);
if (host.hasColorBy) {
container.appendChild(colorByContainer);
}
container.appendChild(metricContainer);
if (host.hasSizeBy) {
container.appendChild(sizeByContainer);
}
// Mount the canvas to the canvas container instead.
host.canvasContainer.appendChild(container);
if (host.hasSizeBy) {
drawSizeByItem(sizeByContainer, sizeByItem, graphicModel.valueSizeBy);
}
});
},
}
);