in powerbi-visual-builder/src_visual/visual.ts [665:1083]
protected updateRun(options: VisualUpdateOptions) {
try {
const dataView = options.dataViews[0];
this.resize(options.viewport.width, options.viewport.height);
const getDatasetResult = this.getDataset(dataView);
if (getDatasetResult == null) {
// If dataset is null, show a warning message
this.divChart.innerHTML = `
<h2>Dataset incomplete. Please specify all data fields.</h2>
`;
this.currentDatasetJSON = null;
this.handleMouseMove = null;
this.unmountContainer();
} else {
try {
CharticulatorContainer.ChartContainer.setFormatOptions({
currency: parseSafe(
(dataView.metadata.objects?.[SettingsNames.General]
.currency as any) || defaultCurrency,
defaultCurrency
),
decimal:
(dataView.metadata.objects?.[SettingsNames.General]
.decimalSeparator as any) || defaultDecimalSeparator,
thousands:
(dataView.metadata.objects?.[SettingsNames.General]
.thousandSeparator as any) || defaultThousandSeparator,
grouping: parseSafe(
(dataView.metadata.objects?.[SettingsNames.General]
.group as any) || defaultGroup,
defaultGroup
)
});
} catch (ex) {
console.warn("Loading localization settings failed");
}
const { dataset } = getDatasetResult;
// Check if dataset is the same
const datasetJSON = JSON.stringify(dataset);
if (datasetJSON != this.currentDatasetJSON) {
this.handleMouseMove = null;
this.unmountContainer();
}
this.currentDatasetJSON = datasetJSON;
// Recreate chartContainer if not exist
if (!this.chartContainer) {
this.divChart.innerHTML = "";
this.chartTemplate.reset();
const defaultTable = this.getDefaultTable(this.template);
const columns = defaultTable.columns as PowerBIColumn[];
this.chartTemplate.assignTable(
defaultTable.name,
defaultTable.name
);
for (const column of columns) {
this.chartTemplate.assignColumn(
defaultTable.name,
column.name,
column.powerBIName || column.name
);
}
// links table
const linksTable = this.getLinksTable(this.template); // todo save table name in property
const links = linksTable && (linksTable.columns as PowerBIColumn[]);
if (links) {
this.chartTemplate.assignTable(linksTable.name, linksTable.name);
for (const column of links) {
this.chartTemplate.assignColumn(
linksTable.name,
column.name,
column.powerBIName || column.name
);
}
}
// tooltips table
const tooltipsTable = this.getTooltipsTable(this.template); // todo save table name in property
const tooltips =
tooltipsTable && (tooltipsTable.columns as PowerBIColumn[]);
if (tooltips) {
this.chartTemplate.assignTable(
tooltipsTable.name,
powerBITooltipsTablename
);
for (const column of tooltips) {
this.chartTemplate.assignColumn(
tooltipsTable.name,
column.name,
column.powerBIName || column.name
);
}
}
const instance = this.chartTemplate.instantiate(dataset);
const { chart } = instance;
// Apply chart properties
for (const property of this.template
.properties as PowerBIProperty[]) {
if (this.properties[property.powerBIName] === undefined) {
continue;
}
const targetProperty = property.target.property;
if (targetProperty) {
if (
typeof targetProperty === "object" &&
(targetProperty.property === "xData" ||
targetProperty.property === "yData" ||
targetProperty.property === "axis") &&
targetProperty.field === "categories"
) {
const direction = this.properties[property.powerBIName];
let values = CharticulatorContainer.ChartTemplate.GetChartProperty(
chart,
property.objectID,
{
property: targetProperty.property,
field: targetProperty.field
}
);
if (values) {
values = this.deepClone(values);
values = (values as string[]).sort();
if (direction === "descending") {
values = (values as string[]).reverse();
}
CharticulatorContainer.ChartTemplate.SetChartProperty(
chart,
property.objectID,
targetProperty,
values
);
}
} else {
if (this.properties[property.powerBIName] != null) {
CharticulatorContainer.ChartTemplate.SetChartProperty(
chart,
property.objectID,
targetProperty,
this.properties[property.powerBIName]
);
}
}
} else {
if (this.properties[property.powerBIName] != null) {
CharticulatorContainer.ChartTemplate.SetChartAttributeMapping(
chart,
property.objectID,
property.target.attribute,
{
type: "value",
value: this.properties[property.powerBIName]
} as CharticulatorContainer.Specification.ValueMapping
);
}
}
}
// Make selection ids:
const selectionIDs: visuals.ISelectionId[] = [];
const selectionID2RowIndex = new WeakMap<ISelectionId, number>();
const rowIndex2selectionID = new Map<number, ISelectionId>();
dataset.tables[0].rows.forEach((row, i) => {
const selectionID = this.host
.createSelectionIdBuilder()
.withCategory(
options.dataViews[0].categorical.categories[0],
getDatasetResult.rowInfo.get(row).index
)
.createSelectionId();
selectionIDs.push(selectionID);
selectionID2RowIndex.set(selectionID, i);
rowIndex2selectionID.set(i, selectionID);
});
this.chartContainer = new CharticulatorContainer.ChartContainer(
instance,
dataset
);
this.chartContainer.addSelectionListener((table, rowIndices) => {
if (
table != null &&
rowIndices != null &&
rowIndices.length > 0
) {
// Power BI's toggle behavior
let alreadySelected = false;
if (this.selectionManager.hasSelection()) {
const ids = this.selectionManager
.getSelectionIds()
.map(id => selectionID2RowIndex.get(id));
if (arrayEquals(ids, rowIndices)) {
alreadySelected = true;
}
}
if (alreadySelected) {
this.selectionManager.clear();
this.chartContainer.clearSelection();
} else {
const ids = rowIndices
.map(i => selectionIDs[i])
.filter(x => x != null);
this.selectionManager.select(ids);
this.chartContainer.setSelection(table, rowIndices);
}
} else {
this.selectionManager.clear();
}
});
if (this.enableDrillDown) {
const service = this.selectionManager;
this.chartContainer.addContextMenuListener(
// TODO change to point object
(table, rowIndices, options) => {
const { clientX, clientY, event } = options;
if ((service as any).showContextMenu) {
const selection = rowIndex2selectionID.get(rowIndices[0]);
(service as any).showContextMenu(selection, {
x: clientX,
y: clientY
});
event.preventDefault();
}
}
);
}
const powerBITooltips = dataset.tables.find(
table => table.name === powerBITooltipsTablename
);
const tooltipsTableColumns = [
...(options.dataViews[0].categorical.categories
? options.dataViews[0].categorical.categories
: []),
...(options.dataViews[0].categorical.values
? options.dataViews[0].categorical.values
: [])
];
const visualHasTooltipData = tooltipsTableColumns.find(
column => column.source.roles.powerBITooltips
);
if (
this.host.tooltipService.enabled() &&
powerBITooltips &&
visualHasTooltipData
) {
const service = this.host.tooltipService;
this.chartContainer.addMouseEnterListener((table, rowIndices) => {
const ids = rowIndices
.map(i => selectionIDs[i])
.filter(x => x != null);
const info = {
coordinates: [this.currentX, this.currentY],
isTouchEvent: false,
dataItems: Array.prototype.concat(
...rowIndices.map(idx => {
const tooltiprow =
powerBITooltips && powerBITooltips.rows[idx];
const row = dataset.tables[0].rows[idx];
return (
Object.keys(tooltiprow)
// excule _id column
.filter(x => x != "_id")
.map(key => {
const header = getDatasetResult.rowInfo.get(row)
.granularity;
let value = tooltiprow[key];
const column = [
...dataset.tables[0].columns,
...(dataset.tables[1]
? dataset.tables[1].columns
: [])
].filter(n => n.name === key)[0];
if (
value !== undefined &&
value !== null &&
column
) {
if (
column.type ===
CharticulatorContainer.Specification.DataType
.Number
) {
value = parseFloat(value + "").toFixed(2);
} else if (
column.type ===
CharticulatorContainer.Specification.DataType
.Date
) {
const numVal = value as number;
if (typeof numVal.toFixed === "function") {
const parsed = new Date(numVal);
value = parsed.toDateString();
}
}
}
return {
displayName: key,
header,
value
};
})
);
})
),
identities: ids
};
service.show(info);
this.handleMouseMove = () => {
service.move({
...info,
coordinates: [this.currentX, this.currentY]
});
};
});
this.chartContainer.addMouseLeaveListener((table, rowIndices) => {
service.hide({ isTouchEvent: false, immediately: false });
this.handleMouseMove = null;
});
this.chartContainer.addMouseLeaveListener(
(table, rowIndices) => {}
);
}
this.chartContainer.mount(this.divChart);
}
if (this.chartContainer) {
// Feed in properties
for (const property of this.template
.properties as PowerBIProperty[]) {
if (this.properties[property.powerBIName] === undefined) {
continue;
}
const targetProperty = property.target.property;
if (targetProperty) {
if (
typeof targetProperty === "object" &&
(targetProperty.property === "xData" ||
targetProperty.property === "yData" ||
targetProperty.property === "axis") &&
targetProperty.field === "categories"
) {
const direction = this.properties[property.powerBIName];
let values = this.chartContainer.getProperty(
property.objectID,
{
property: targetProperty.property,
field: targetProperty.field
}
);
if (values) {
values = this.deepClone(values);
values = (values as string[]).sort();
if (direction === "descending") {
values = (values as string[]).reverse();
}
this.chartContainer.setProperty(
property.objectID,
targetProperty,
values
);
}
} else {
if (this.properties[property.powerBIName] != null) {
this.chartContainer.setProperty(
property.objectID,
targetProperty,
this.properties[property.powerBIName]
);
}
}
}
if (
property.target.attribute &&
this.properties[property.powerBIName] != null
) {
this.chartContainer.setAttributeMapping(
property.objectID,
property.target.attribute,
{
type: "value",
value: this.properties[property.powerBIName]
} as CharticulatorContainer.Specification.ValueMapping
);
}
}
if (getDatasetResult && getDatasetResult.rowInfo) {
const rows = dataset.tables[0].rows;
const indices = [];
for (let i = 0; i < rows.length; i++) {
if (getDatasetResult.rowInfo.get(rows[i]).highlight) {
indices.push(i);
}
}
if (indices.length > 0) {
const defaultTable = this.getDefaultTable(this.template);
this.chartContainer.setSelection(defaultTable.name, indices);
} else {
this.chartContainer.clearSelection();
}
}
this.chartContainer.resize(
options.viewport.width,
options.viewport.height
);
}
}
} catch (e) {
console.log(e);
}
}