Sankey 3.0.3 release (#94)
* version upgrade * context menu on clear catcher added * selection quick fix * matrix selection wip * updated packages * position bug fix * contrast mode label display bug fix * 3.0.3 * deleted identityIndex from node label * check * Delete yarn.lock * code cleanup in sankeyDIagrams.ts Co-authored-by: Nikita Grachev <v-grniki@microsoft.com>
This commit is contained in:
Родитель
d7db394c69
Коммит
3abaecf013
|
@ -1,3 +1,7 @@
|
|||
## 3.0.1
|
||||
* Context menu fix
|
||||
* Selection fix
|
||||
|
||||
## 2.1.0
|
||||
* Updated APIs
|
||||
* Sorting added
|
||||
|
|
|
@ -6,29 +6,28 @@
|
|||
"displayName": "Source",
|
||||
"displayNameKey": "Visual_Source"
|
||||
},
|
||||
{
|
||||
"name": "SourceLabels",
|
||||
"kind": "Measure",
|
||||
"displayName": "Source labels",
|
||||
"displayNameKey": "Visual_SourceLabels"
|
||||
},
|
||||
{
|
||||
"name": "Destination",
|
||||
"kind": "Grouping",
|
||||
"kind": "GroupingOrMeasure",
|
||||
"displayName": "Destination",
|
||||
"displayNameKey": "Visual_Destination"
|
||||
},
|
||||
{
|
||||
"name": "DestinationLabels",
|
||||
"kind": "Measure",
|
||||
"displayName": "Destination labels",
|
||||
"displayNameKey": "Visual_DestinationLabels"
|
||||
},
|
||||
{
|
||||
"name": "Weight",
|
||||
"kind": "Measure",
|
||||
"displayName": "Weight",
|
||||
"displayNameKey": "Visual_Weight"
|
||||
"displayNameKey": "Visual_Weight",
|
||||
"requiredTypes": [
|
||||
{
|
||||
"numeric": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "SourceLabels",
|
||||
"kind": "Measure",
|
||||
"displayName": "Source labels",
|
||||
"displayNameKey": "Visual_SourceLabels"
|
||||
}
|
||||
],
|
||||
"dataViewMappings": [
|
||||
|
@ -36,7 +35,7 @@
|
|||
"conditions": [
|
||||
{
|
||||
"Source": {
|
||||
"min": 0,
|
||||
"min": 1,
|
||||
"max": 1
|
||||
},
|
||||
"Destination": {
|
||||
|
@ -47,10 +46,6 @@
|
|||
"min": 0,
|
||||
"max": 1
|
||||
},
|
||||
"DestinationLabels": {
|
||||
"min": 0,
|
||||
"max": 1
|
||||
},
|
||||
"Weight": {
|
||||
"min": 0,
|
||||
"max": 1
|
||||
|
@ -66,8 +61,8 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"for": {
|
||||
"in": "Destination"
|
||||
"bind": {
|
||||
"to": "Destination"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -79,11 +74,6 @@
|
|||
"in": "SourceLabels"
|
||||
}
|
||||
},
|
||||
{
|
||||
"for": {
|
||||
"in": "DestinationLabels"
|
||||
}
|
||||
},
|
||||
{
|
||||
"for": {
|
||||
"in": "Weight"
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
74
package.json
74
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "powerbi-visuals-sankey",
|
||||
"version": "2.1.3",
|
||||
"version": "3.0.3",
|
||||
"description": "Sankey is a type of flow diagram in which the width of the series is in proportion to the quantity of the flow. Use it to find major contributions to an overall flow.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -16,7 +16,7 @@
|
|||
"package": "pbiviz package",
|
||||
"lint": "tslint -c tslint.json -p tsconfig.json --fix",
|
||||
"test": "karma start",
|
||||
"cert": "pbiviz --create-cert"
|
||||
"cert": "pbiviz --install-cert"
|
||||
},
|
||||
"author": {
|
||||
"name": "Microsoft",
|
||||
|
@ -28,53 +28,53 @@
|
|||
},
|
||||
"homepage": "https://github.com/Microsoft/powerbi-visuals-sankey#readme",
|
||||
"devDependencies": {
|
||||
"@types/d3": "5.7.2",
|
||||
"@types/jasmine": "3.5.2",
|
||||
"@types/d3": "6.2.0",
|
||||
"@types/jasmine": "3.6.2",
|
||||
"@types/jasmine-jquery": "1.5.33",
|
||||
"@types/jquery": "3.3.31",
|
||||
"@types/karma": "3.0.5",
|
||||
"@types/jquery": "3.5.5",
|
||||
"@types/karma": "5.0.1",
|
||||
"@types/lodash-es": "4.17.3",
|
||||
"coveralls": "3.0.9",
|
||||
"d3": "5.15.0",
|
||||
"globalize": "1.4.2",
|
||||
"coveralls": "3.1.0",
|
||||
"d3": "^5.16.0",
|
||||
"globalize": "1.6.0",
|
||||
"istanbul-instrumenter-loader": "^3.0.1",
|
||||
"jasmine": "3.5.0",
|
||||
"jasmine-core": "3.5.0",
|
||||
"jasmine": "3.6.3",
|
||||
"jasmine-core": "3.6.0",
|
||||
"jasmine-jquery": "2.1.1",
|
||||
"jquery": "^3.4.1",
|
||||
"karma": "^4.4.1",
|
||||
"jquery": "^3.5.1",
|
||||
"karma": "^5.2.3",
|
||||
"karma-chrome-launcher": "3.1.0",
|
||||
"karma-coverage": "2.0.1",
|
||||
"karma-coverage-istanbul-reporter": "^2.1.1",
|
||||
"karma-jasmine": "3.1.0",
|
||||
"karma-coverage": "2.0.3",
|
||||
"karma-coverage-istanbul-reporter": "^3.0.3",
|
||||
"karma-jasmine": "4.0.1",
|
||||
"karma-junit-reporter": "^2.0.1",
|
||||
"karma-sourcemap-loader": "^0.3.7",
|
||||
"karma-typescript": "4.1.1",
|
||||
"karma-sourcemap-loader": "^0.3.8",
|
||||
"karma-typescript": "5.2.0",
|
||||
"karma-typescript-preprocessor": "0.4.0",
|
||||
"karma-webpack": "4.0.2",
|
||||
"lodash-es": "4.17.15",
|
||||
"powerbi-models": "1.3.1",
|
||||
"powerbi-visuals-api": "^2.6.2",
|
||||
"powerbi-visuals-tools": "^3.1.10",
|
||||
"powerbi-visuals-utils-colorutils": "^2.2.1",
|
||||
"powerbi-visuals-utils-dataviewutils": "^2.2.1",
|
||||
"powerbi-visuals-utils-formattingutils": "^4.5.0",
|
||||
"powerbi-visuals-utils-interactivityutils": "5.6.0",
|
||||
"powerbi-visuals-utils-svgutils": "^2.2.1",
|
||||
"powerbi-visuals-utils-testutils": "^2.2.1",
|
||||
"powerbi-visuals-utils-tooltiputils": "^2.3.1",
|
||||
"powerbi-visuals-utils-typeutils": "^2.2.1",
|
||||
"puppeteer": "^2.1.1",
|
||||
"style-loader": "1.1.3",
|
||||
"ts-loader": "6.2.1",
|
||||
"ts-node": "8.6.2",
|
||||
"powerbi-models": "1.7.0",
|
||||
"powerbi-visuals-api": "^3.4.0",
|
||||
"powerbi-visuals-tools": "^3.1.15",
|
||||
"powerbi-visuals-utils-colorutils": "^2.3.0",
|
||||
"powerbi-visuals-utils-dataviewutils": "^2.4.0",
|
||||
"powerbi-visuals-utils-formattingutils": "^4.7.0",
|
||||
"powerbi-visuals-utils-interactivityutils": "5.7.0",
|
||||
"powerbi-visuals-utils-svgutils": "^2.3.0",
|
||||
"powerbi-visuals-utils-testutils": "^2.3.4",
|
||||
"powerbi-visuals-utils-tooltiputils": "^2.4.0",
|
||||
"powerbi-visuals-utils-typeutils": "^2.3.0",
|
||||
"puppeteer": "^5.5.0",
|
||||
"style-loader": "2.0.0",
|
||||
"ts-loader": "8.0.11",
|
||||
"ts-node": "9.1.1",
|
||||
"tslint": "^6.0.0",
|
||||
"tslint-microsoft-contrib": "^6.2.0",
|
||||
"typescript": "^3.7.5",
|
||||
"webpack": "4.41.5"
|
||||
"typescript": "^4.1.2",
|
||||
"webpack": "5.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": "^3.6.4",
|
||||
"serialize-javascript": "^2.1.2"
|
||||
"core-js": "^3.8.1",
|
||||
"serialize-javascript": "^5.0.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
{
|
||||
"visual": {
|
||||
"name": "SankeyDiagram",
|
||||
"displayName": "Sankey 2.1.3",
|
||||
"guid": "SankeyDiagram1446463184954",
|
||||
"displayName": "Sankey 3.0.3",
|
||||
"guid": "sankey02300D1BE6F5427989F3DE31CCA9E0F32020",
|
||||
"visualClassName": "SankeyDiagram",
|
||||
"version": "2.1.3",
|
||||
"version": "3.0.3",
|
||||
"description": "Sankey is a type of flow diagram in which the width of the series is in proportion to the quantity of the flow. Use it to find major contributions to an overall flow.",
|
||||
"supportUrl": "https://community.powerbi.com",
|
||||
"gitHubUrl": "https://github.com/Microsoft/powerbi-visuals-sankey"
|
||||
},
|
||||
"apiVersion": "2.6.0",
|
||||
"apiVersion": "3.4.0",
|
||||
"author": {
|
||||
"name": "Microsoft",
|
||||
"email": "pbicvsupport@microsoft.com"
|
||||
|
|
|
@ -136,6 +136,18 @@ export class SankeyDiagramBehavior implements IInteractiveBehavior {
|
|||
}
|
||||
|
||||
private bindClickEventToClearCatcher(): void {
|
||||
this.behaviorOptions.clearCatcher.on("contextmenu", () => {
|
||||
const event: MouseEvent = (<MouseEvent>getEvent()) || <MouseEvent>window.event;
|
||||
if (event) {
|
||||
this.selectionHandler.handleContextMenu(
|
||||
null,
|
||||
{
|
||||
x: event.clientX,
|
||||
y: event.clientY
|
||||
});
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
this.behaviorOptions.clearCatcher.on("click", () => {
|
||||
this.clearSelection();
|
||||
this.createAnEmptySelectedDataPoints();
|
||||
|
|
|
@ -85,6 +85,7 @@ export interface SankeyDiagramLink extends
|
|||
TooltipEnabledDataPoint,
|
||||
SelectableDataPoint {
|
||||
|
||||
label: string;
|
||||
source: SankeyDiagramNode;
|
||||
destination: SankeyDiagramNode;
|
||||
weigth: number;
|
||||
|
|
|
@ -52,8 +52,9 @@ import DataViewMetadataColumn = powerbi.DataViewMetadataColumn;
|
|||
import DataViewValueColumns = powerbi.DataViewValueColumns;
|
||||
import VisualObjectInstanceEnumerationObject = powerbi.VisualObjectInstanceEnumerationObject;
|
||||
import DataViewMatrixNode = powerbi.DataViewMatrixNode;
|
||||
|
||||
import DataViewMatrix = powerbi.DataViewMatrix;
|
||||
// powerbi.visuals
|
||||
import ISelectionIdBuilder = powerbi.visuals.ISelectionIdBuilder;
|
||||
import ISelectionId = powerbi.visuals.ISelectionId;
|
||||
|
||||
// powerbi.extensibility
|
||||
|
@ -131,6 +132,7 @@ import {
|
|||
SankeyDiagramBehaviorOptions,
|
||||
SankeyDiagramBehavior
|
||||
} from "./behavior";
|
||||
import { data } from "jquery";
|
||||
|
||||
export class SankeyDiagram implements IVisual {
|
||||
private static ClassName: string = "sankeyDiagram";
|
||||
|
@ -322,32 +324,28 @@ export class SankeyDiagram implements IVisual {
|
|||
|
||||
public update(visualUpdateOptions: VisualUpdateOptions): void {
|
||||
this.visualHost.eventService.renderingStarted(visualUpdateOptions);
|
||||
try {
|
||||
let sankeyDiagramDataView: SankeyDiagramDataView,
|
||||
viewport: IViewport = visualUpdateOptions
|
||||
&& visualUpdateOptions.viewport
|
||||
|| SankeyDiagram.DefaultViewport,
|
||||
dataView: DataView = visualUpdateOptions
|
||||
&& visualUpdateOptions.dataViews
|
||||
&& visualUpdateOptions.dataViews[0];
|
||||
|
||||
let sankeyDiagramDataView: SankeyDiagramDataView,
|
||||
viewport: IViewport = visualUpdateOptions
|
||||
&& visualUpdateOptions.viewport
|
||||
|| SankeyDiagram.DefaultViewport,
|
||||
dataView: DataView = visualUpdateOptions
|
||||
&& visualUpdateOptions.dataViews
|
||||
&& visualUpdateOptions.dataViews[0];
|
||||
|
||||
this.updateViewport(visualUpdateOptions.viewport);
|
||||
|
||||
this.updateViewport(visualUpdateOptions.viewport);
|
||||
sankeyDiagramDataView = this.converter(dataView);
|
||||
|
||||
sankeyDiagramDataView = this.converter(dataView);
|
||||
this.computePositions(sankeyDiagramDataView);
|
||||
|
||||
this.computePositions(sankeyDiagramDataView);
|
||||
this.dataView = sankeyDiagramDataView;
|
||||
|
||||
this.dataView = sankeyDiagramDataView;
|
||||
this.applySelectionStateToData();
|
||||
|
||||
this.applySelectionStateToData();
|
||||
|
||||
this.render(sankeyDiagramDataView);
|
||||
this.visualHost.eventService.renderingFinished(visualUpdateOptions);
|
||||
}
|
||||
catch (e) {
|
||||
this.visualHost.eventService.renderingFailed(visualUpdateOptions, e);
|
||||
console.log(e);
|
||||
}
|
||||
this.render(sankeyDiagramDataView);
|
||||
this.visualHost.eventService.renderingFinished(visualUpdateOptions);
|
||||
|
||||
}
|
||||
|
||||
private updateViewport(viewport: IViewport): void {
|
||||
|
@ -380,6 +378,48 @@ export class SankeyDiagram implements IVisual {
|
|||
this.main.attr("transform", translate(this.margin.left, this.margin.top));
|
||||
}
|
||||
|
||||
private createNewNode(node: DataViewMatrixNode, settings: SankeyDiagramSettings): SankeyDiagramNode {
|
||||
let nodeFillColor = this.getColor(
|
||||
SankeyDiagram.NodesPropertyIdentifier,
|
||||
this.colorPalette.getColor(<string>node.value).value,
|
||||
<any>node.objects);
|
||||
let nodeStrokeColor = this.colorHelper.getHighContrastColor("foreground", nodeFillColor);
|
||||
|
||||
let name = <any>node.value;
|
||||
|
||||
let textProperties: TextProperties = {
|
||||
text: name,
|
||||
fontFamily: this.textProperties.fontFamily,
|
||||
fontSize: this.textProperties.fontSize
|
||||
};
|
||||
let label: SankeyDiagramLabel = {
|
||||
internalName: name,
|
||||
name: name,
|
||||
formattedName: name,//valueFormatterForCategories.format((<string>labelsDictionary[item].toString()).replace(SankeyDiagram.DuplicatedNamePostfix, "")),
|
||||
width: textMeasurementService.measureSvgTextWidth(textProperties),
|
||||
height: textMeasurementService.estimateSvgTextHeight(textProperties),
|
||||
color: settings.labels.fill
|
||||
};
|
||||
|
||||
return {
|
||||
label: label,
|
||||
links: [],
|
||||
inputWeight: 0,
|
||||
outputWeight: 0,
|
||||
backwardWeight: 0,
|
||||
selftLinkWeight: 0,
|
||||
width: 10, //fix
|
||||
height: 0,
|
||||
fillColor: nodeFillColor,
|
||||
strokeColor: nodeStrokeColor,
|
||||
tooltipInfo: [],
|
||||
selectableDataPoints: [],
|
||||
settings: null,
|
||||
identity: null,
|
||||
selected: false
|
||||
}
|
||||
}
|
||||
|
||||
// tslint:disable-next-line: max-func-body-length
|
||||
public converter(dataView: DataView): SankeyDiagramDataView {
|
||||
const settings: SankeyDiagramSettings = this.parseSettings(dataView);
|
||||
|
@ -399,16 +439,16 @@ export class SankeyDiagram implements IVisual {
|
|||
|| !dataView.matrix.rows.root
|
||||
|| !dataView.matrix.rows.root.children
|
||||
|| !dataView.matrix.valueSources) {
|
||||
return {
|
||||
settings,
|
||||
nodes: [],
|
||||
links: [],
|
||||
columns: []
|
||||
}
|
||||
return {
|
||||
settings,
|
||||
nodes: [],
|
||||
links: [],
|
||||
columns: []
|
||||
}
|
||||
}
|
||||
|
||||
let nodes: SankeyDiagramNode[],
|
||||
links: SankeyDiagramLink[],
|
||||
let nodes: SankeyDiagramNode[] = [],
|
||||
links: SankeyDiagramLink[] = [],
|
||||
categories: any[] = [],
|
||||
sourceCategories: any[] = [],
|
||||
destinationCategories: any[] = [],
|
||||
|
@ -419,82 +459,150 @@ export class SankeyDiagram implements IVisual {
|
|||
selectionIdBuilder: SelectionIdBuilder = new SelectionIdBuilder(
|
||||
this.visualHost,
|
||||
dataView.matrix),
|
||||
valueSources = dataView.matrix.valueSources,
|
||||
sourceLabelIndex: number,
|
||||
destinationLabelIndex: number,
|
||||
weightIndex: number;
|
||||
valueSources = dataView.matrix.valueSources;
|
||||
|
||||
sourceLabelIndex = valueSources.indexOf(valueSources.filter((column: powerbi.DataViewMetadataColumn) => {
|
||||
|
||||
|
||||
const sourceLabelIndex: number = valueSources.indexOf(valueSources.filter((column: powerbi.DataViewMetadataColumn) => {
|
||||
return column.roles.SourceLabels;
|
||||
}).pop());
|
||||
destinationLabelIndex = valueSources.indexOf(valueSources.filter((source: powerbi.DataViewMetadataColumn) => {
|
||||
return source.roles.DestinationLabels;
|
||||
}).pop());
|
||||
weightIndex = valueSources.indexOf(valueSources.filter((source: powerbi.DataViewMetadataColumn) => {
|
||||
|
||||
|
||||
const weightIndex: number = valueSources.indexOf(valueSources.filter((source: powerbi.DataViewMetadataColumn) => {
|
||||
return source.roles.Weight;
|
||||
}).pop());
|
||||
|
||||
dataView.matrix.rows.root.children.forEach((source: DataViewMatrixNode) =>{
|
||||
objects.push(source.objects);
|
||||
const sourceFieldName = dataView.matrix.rows.levels[0].sources[0].displayName;
|
||||
const destinationFieldName = dataView.matrix.rows.levels[1].sources[0].displayName;
|
||||
const valueFieldName = dataView.matrix.valueSources[weightIndex] ? dataView.matrix.valueSources[weightIndex].displayName : null;
|
||||
|
||||
const formatOfWeigth = valueFormatter.getFormatStringByColumn(valueSources[weightIndex]);
|
||||
|
||||
|
||||
let weightValues: number[] = [1];
|
||||
|
||||
dataView.matrix.rows.root.children.forEach(parent => {
|
||||
let newSourceNode = this.createNewNode(parent, settings)
|
||||
newSourceNode.identity = this.visualHost.createSelectionIdBuilder()
|
||||
.withMatrixNode(parent, dataView.matrix.rows.levels)
|
||||
.createSelectionId();
|
||||
nodes.push(newSourceNode);
|
||||
|
||||
});
|
||||
|
||||
dataView.matrix.rows.root.children.forEach((source: DataViewMatrixNode) =>{
|
||||
categories.push(source.levelValues[0].value);
|
||||
|
||||
source.children.forEach((destination: DataViewMatrixNode) => {
|
||||
sourceCategories.push(source.levelValues[0].value);
|
||||
destinationCategories.push(destination.levelValues[0].value);
|
||||
dataView.matrix.rows.root.children.forEach(parent => {
|
||||
let foundSource: SankeyDiagramNode = nodes.find(found => found.label.name === parent.value)
|
||||
|
||||
// If both source and destination labels are present in DataView, populate appropiate arrays
|
||||
if (sourceLabelIndex != -1 && destinationLabelIndex != -1)
|
||||
{
|
||||
sourceCategoriesLabels.push(destination.values[sourceLabelIndex].value);
|
||||
destinationCategoriesLabels.push(destination.values[destinationLabelIndex].value);
|
||||
parent.children.forEach(child => {
|
||||
let linkLabel = undefined;
|
||||
let weigth: any = SankeyDiagram.DefaultWeightValue;
|
||||
|
||||
let foundDestination: SankeyDiagramNode = nodes.find(found => found.label.name === child.value)
|
||||
if (!foundDestination) {
|
||||
foundDestination = this.createNewNode(child, settings);
|
||||
foundDestination.identity = this.visualHost.createSelectionIdBuilder()
|
||||
.withMatrixNode(parent, dataView.matrix.rows.levels)
|
||||
.withMatrixNode(child, dataView.matrix.rows.levels)
|
||||
.createSelectionId();
|
||||
nodes.push(foundDestination);
|
||||
}
|
||||
|
||||
if (sourceLabelIndex != -1) {
|
||||
linkLabel = (child.values[sourceLabelIndex] && child.values[sourceLabelIndex].value) ?
|
||||
child.values[sourceLabelIndex].value || SankeyDiagram.DefaultWeightValue : SankeyDiagram.MinWeightValue;
|
||||
}
|
||||
|
||||
|
||||
// If weights are present, populate the weights array
|
||||
if (weightIndex != -1)
|
||||
{
|
||||
weights.push(destination.values[weightIndex].value);
|
||||
if (weightIndex != -1) {
|
||||
weigth = (child.values[weightIndex] && child.values[weightIndex].value) ?
|
||||
child.values[weightIndex].value || SankeyDiagram.DefaultWeightValue : SankeyDiagram.MinWeightValue;
|
||||
|
||||
weightValues.push(weigth);
|
||||
|
||||
}
|
||||
objects.push(destination.objects);
|
||||
categories.push(destination.levelValues[0].value);
|
||||
|
||||
let linkFillColor = this.getColor(
|
||||
SankeyDiagram.LinksPropertyIdentifier,
|
||||
SankeyDiagram.DefaultColourOfLink,
|
||||
child.objects);
|
||||
let linkStrokeColor = this.colorHelper.isHighContrast ? this.colorHelper.getHighContrastColor("foreground", linkFillColor) : linkFillColor;
|
||||
|
||||
|
||||
let tooltipInfo = SankeyDiagram.getTooltipDataForLink(
|
||||
valuesFormatterForWeigth,
|
||||
foundSource.label.formattedName,
|
||||
foundDestination.label.formattedName,
|
||||
weigth,
|
||||
sourceFieldName,
|
||||
destinationFieldName,
|
||||
valueFieldName
|
||||
);
|
||||
|
||||
let link: SankeyDiagramLink = {
|
||||
label: linkLabel && linkLabel.toString(),
|
||||
source: foundSource,
|
||||
destination: foundDestination,
|
||||
weigth: weigth,
|
||||
height: 10,
|
||||
fillColor: linkFillColor,
|
||||
strokeColor: linkStrokeColor,
|
||||
dySource: 0,
|
||||
dyDestination: 0,
|
||||
tooltipInfo: tooltipInfo,
|
||||
identity: this.visualHost.createSelectionIdBuilder()
|
||||
.withMatrixNode(parent, dataView.matrix.rows.levels)
|
||||
.withMatrixNode(child, dataView.matrix.rows.levels)
|
||||
.createSelectionId(),
|
||||
selected: false,
|
||||
direction: SankeyLinkDirrections.Forward
|
||||
}
|
||||
|
||||
let selectableDataPoint: SelectableDataPoint = SankeyDiagram.createSelectableDataPoint(<ISelectionId>link.identity);
|
||||
foundSource.selectableDataPoints.push(selectableDataPoint);
|
||||
foundDestination.selectableDataPoints.push(selectableDataPoint);
|
||||
|
||||
links.push(link);
|
||||
|
||||
foundSource.links.push(link);
|
||||
foundDestination.links.push(link);
|
||||
|
||||
SankeyDiagram.updateValueOfNode(foundSource);
|
||||
SankeyDiagram.updateValueOfNode(foundDestination);
|
||||
});
|
||||
});
|
||||
|
||||
const valuesFormatterForWeigth = valueFormatter.create({
|
||||
format: formatOfWeigth,
|
||||
value: Math.max(
|
||||
settings.labels.unit !== 0 ? settings.labels.unit : d3.max(weightValues) || SankeyDiagram.MinWeightValue,
|
||||
SankeyDiagram.MinWeightValue),
|
||||
});
|
||||
|
||||
|
||||
nodes = this.createNodes(
|
||||
categories,
|
||||
sourceCategories,
|
||||
destinationCategories,
|
||||
settings,
|
||||
selectionIdBuilder,
|
||||
dataView.matrix.rows.levels[0].sources[0],
|
||||
objects,
|
||||
sourceCategoriesLabels,
|
||||
destinationCategoriesLabels);
|
||||
|
||||
links = this.createLinks(
|
||||
nodes,
|
||||
sourceCategories,
|
||||
destinationCategories,
|
||||
settings,
|
||||
selectionIdBuilder,
|
||||
weights,
|
||||
objects,
|
||||
dataView.matrix.rows.levels[0].sources[0].displayName,
|
||||
dataView.matrix.rows.levels[1].sources[0].displayName,
|
||||
dataView.matrix.valueSources[weightIndex] ? dataView.matrix.valueSources[weightIndex].displayName : null,
|
||||
dataView.matrix.valueSources
|
||||
);
|
||||
|
||||
let cycles: SankeyDiagramCycleDictionary = this.checkCycles(nodes);
|
||||
|
||||
if (settings.cyclesLinks.drawCycles === CyclesDrawType.Duplicate) {
|
||||
links = this.processCyclesForwardLinks(cycles, nodes, links, settings);
|
||||
}
|
||||
|
||||
nodes.forEach((node: SankeyDiagramNode) => {
|
||||
node.tooltipInfo = SankeyDiagram.getTooltipForNode(
|
||||
valuesFormatterForWeigth,
|
||||
node.label.formattedName,
|
||||
node.inputWeight
|
||||
? node.inputWeight
|
||||
: node.outputWeight,
|
||||
this.localizationManager,
|
||||
node.inputWeight > 0 && node.outputWeight > 0 ? `${sourceFieldName}-${destinationFieldName}` : node.outputWeight > 0
|
||||
? sourceFieldName
|
||||
: destinationFieldName,
|
||||
valueFieldName
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
let sankeyDiagramDataView = {
|
||||
nodes,
|
||||
links,
|
||||
|
@ -511,10 +619,8 @@ export class SankeyDiagram implements IVisual {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.checkNodePositionSettings(nodes, settings);
|
||||
this.restoreNodePositions(nodes, settings);
|
||||
|
||||
return sankeyDiagramDataView;
|
||||
}
|
||||
|
||||
|
@ -699,219 +805,8 @@ export class SankeyDiagram implements IVisual {
|
|||
return simpleCycles;
|
||||
}
|
||||
|
||||
private createNodes(
|
||||
categories: any[],
|
||||
sourceCategories: any[],
|
||||
destinationCategories: any[],
|
||||
settings: SankeyDiagramSettings,
|
||||
selectionIdBuilder: SelectionIdBuilder,
|
||||
source: DataViewMetadataColumn,
|
||||
objects: DataViewObjects[],
|
||||
sourceCategoriesLabels?: any[],
|
||||
destinationCategoriesLabels?: any[]): SankeyDiagramNode[] {
|
||||
|
||||
let nodes: SankeyDiagramNode[] = [],
|
||||
valueFormatterForCategories: IValueFormatter,
|
||||
nodeFillColor: string,
|
||||
nodeStrokeColor: string,
|
||||
selectionId: ISelectionId;
|
||||
|
||||
valueFormatterForCategories = valueFormatter.create({
|
||||
format: valueFormatter.getFormatStringByColumn(source),
|
||||
value: sourceCategories[0],
|
||||
value2: destinationCategories[destinationCategories.length - 1]
|
||||
});
|
||||
|
||||
// check self connected links
|
||||
for (let index: number = 0; index < destinationCategories.length; index++) {
|
||||
if (sourceCategoriesLabels[index] === undefined) {
|
||||
sourceCategoriesLabels[index] = sourceCategories[index];
|
||||
}
|
||||
if (destinationCategoriesLabels[index] === undefined) {
|
||||
destinationCategoriesLabels[index] = destinationCategories[index];
|
||||
}
|
||||
}
|
||||
|
||||
let labelsDictionary: Object = {};
|
||||
sourceCategories.forEach((item: any, index: number) => {
|
||||
labelsDictionary[item] = sourceCategoriesLabels[index] || "";
|
||||
});
|
||||
destinationCategories.forEach((item: any, index: number) => {
|
||||
labelsDictionary[item] = destinationCategoriesLabels[index] || "";
|
||||
});
|
||||
|
||||
categories.forEach((item: any, index: number) => {
|
||||
let formattedValue: string = valueFormatterForCategories.format((<string>labelsDictionary[item].toString()).replace(SankeyDiagram.DuplicatedNamePostfix, "")),
|
||||
label: SankeyDiagramLabel,
|
||||
selectableDataPoint: SelectableDataPoint,
|
||||
textProperties: TextProperties = {
|
||||
text: formattedValue,
|
||||
fontFamily: this.textProperties.fontFamily,
|
||||
fontSize: this.textProperties.fontSize
|
||||
};
|
||||
|
||||
label = {
|
||||
internalName: item,
|
||||
name: item,
|
||||
formattedName: valueFormatterForCategories.format((<string>labelsDictionary[item].toString()).replace(SankeyDiagram.DuplicatedNamePostfix, "")),
|
||||
width: textMeasurementService.measureSvgTextWidth(textProperties),
|
||||
height: textMeasurementService.estimateSvgTextHeight(textProperties),
|
||||
color: settings.labels.fill
|
||||
};
|
||||
nodeFillColor = this.getColor(
|
||||
SankeyDiagram.NodesPropertyIdentifier,
|
||||
this.colorPalette.getColor(item).value,
|
||||
objects[index]);
|
||||
nodeStrokeColor = this.colorHelper.getHighContrastColor("foreground", nodeFillColor);
|
||||
|
||||
selectionId = selectionIdBuilder.createSelectionId(index);
|
||||
|
||||
if (nodes.filter((node: SankeyDiagramNode) => {
|
||||
return node.label.name === item;
|
||||
}).length === 0)
|
||||
|
||||
nodes.push({
|
||||
label: label,
|
||||
links: [],
|
||||
inputWeight: 0,
|
||||
outputWeight: 0,
|
||||
backwardWeight: 0,
|
||||
selftLinkWeight: 0,
|
||||
width: this.nodeWidth,
|
||||
height: 0,
|
||||
fillColor: nodeFillColor,
|
||||
strokeColor: nodeStrokeColor,
|
||||
tooltipInfo: [],
|
||||
selectableDataPoints: [],
|
||||
settings: null,
|
||||
identity: selectionId,
|
||||
selected: false
|
||||
});
|
||||
});
|
||||
return nodes;
|
||||
}
|
||||
|
||||
// tslint:disable-next-line: max-func-body-length
|
||||
private createLinks(
|
||||
nodes: SankeyDiagramNode[],
|
||||
sourceCategories: any[],
|
||||
destinationCategories: any[],
|
||||
settings: SankeyDiagramSettings,
|
||||
selectionIdBuilder: SelectionIdBuilder,
|
||||
weights: any[],
|
||||
objects: DataViewObjects[],
|
||||
sourceFieldName: string,
|
||||
destinationFieldName: string,
|
||||
valueFieldName: string,
|
||||
valueSources: any
|
||||
): SankeyDiagramLink[] {
|
||||
|
||||
let links: SankeyDiagramLink[] = [],
|
||||
weightValues: number[] = [],
|
||||
dataPoints: SankeyDiagramDataPoint[] = [],
|
||||
valuesFormatterForWeigth: IValueFormatter,
|
||||
formatOfWeigth: string = SankeyDiagram.DefaultFormatOfWeigth;
|
||||
|
||||
formatOfWeigth = valueFormatter.getFormatStringByColumn(valueSources);
|
||||
|
||||
dataPoints = sourceCategories.map((item: any, index: number) => {
|
||||
return {
|
||||
source: item,
|
||||
destination: destinationCategories[index],
|
||||
weigth: weights[index]
|
||||
? weights[index] || SankeyDiagram.DefaultWeightValue
|
||||
: SankeyDiagram.MinWeightValue
|
||||
};
|
||||
});
|
||||
|
||||
valuesFormatterForWeigth = valueFormatter.create({
|
||||
format: formatOfWeigth,
|
||||
value: Math.max(
|
||||
settings.labels.unit !== 0 ? settings.labels.unit : d3.max(weightValues) || SankeyDiagram.MinWeightValue,
|
||||
SankeyDiagram.MinWeightValue),
|
||||
});
|
||||
|
||||
dataPoints.forEach((dataPoint: SankeyDiagramDataPoint, index: number) => {
|
||||
let sourceNode: SankeyDiagramNode,
|
||||
destinationNode: SankeyDiagramNode,
|
||||
link: SankeyDiagramLink,
|
||||
linkFillColor: string,
|
||||
linkStrokeColor: string,
|
||||
selectionId: ISelectionId;
|
||||
|
||||
nodes.forEach((node: SankeyDiagramNode) => {
|
||||
if (node.label.internalName === dataPoint.source) {
|
||||
sourceNode = node;
|
||||
}
|
||||
|
||||
if (node.label.internalName === dataPoint.destination) {
|
||||
destinationNode = node;
|
||||
}
|
||||
});
|
||||
|
||||
linkFillColor = this.getColor(
|
||||
SankeyDiagram.LinksPropertyIdentifier,
|
||||
SankeyDiagram.DefaultColourOfLink,
|
||||
objects[index]);
|
||||
linkStrokeColor = this.colorHelper.isHighContrast ? this.colorHelper.getHighContrastColor("foreground", linkFillColor) : linkFillColor;
|
||||
|
||||
selectionId = selectionIdBuilder.createSelectionId(index);
|
||||
|
||||
link = {
|
||||
source: sourceNode,
|
||||
destination: destinationNode,
|
||||
weigth: dataPoint.weigth,
|
||||
height: dataPoint.weigth,
|
||||
fillColor: linkFillColor,
|
||||
strokeColor: linkStrokeColor,
|
||||
dySource: 0,
|
||||
dyDestination: 0,
|
||||
tooltipInfo: SankeyDiagram.getTooltipDataForLink(
|
||||
valuesFormatterForWeigth,
|
||||
sourceNode.label.formattedName,
|
||||
destinationNode.label.formattedName,
|
||||
dataPoint.weigth,
|
||||
sourceFieldName,
|
||||
destinationFieldName,
|
||||
valueFieldName
|
||||
),
|
||||
identity: selectionId,
|
||||
selected: false,
|
||||
direction: SankeyLinkDirrections.Forward
|
||||
};
|
||||
|
||||
let selectableDataPoint: SelectableDataPoint = SankeyDiagram.createSelectableDataPoint(selectionId);
|
||||
sourceNode.selectableDataPoints.push(selectableDataPoint);
|
||||
destinationNode.selectableDataPoints.push(selectableDataPoint);
|
||||
|
||||
links.push(link);
|
||||
|
||||
sourceNode.links.push(link);
|
||||
destinationNode.links.push(link);
|
||||
|
||||
SankeyDiagram.updateValueOfNode(sourceNode);
|
||||
SankeyDiagram.updateValueOfNode(destinationNode);
|
||||
});
|
||||
|
||||
nodes.forEach((nodes: SankeyDiagramNode) => {
|
||||
nodes.tooltipInfo = SankeyDiagram.getTooltipForNode(
|
||||
valuesFormatterForWeigth,
|
||||
nodes.label.formattedName,
|
||||
nodes.inputWeight
|
||||
? nodes.inputWeight
|
||||
: nodes.outputWeight,
|
||||
this.localizationManager,
|
||||
nodes.inputWeight > 0 && nodes.outputWeight > 0 ? `${sourceFieldName}-${destinationFieldName}` : nodes.outputWeight > 0
|
||||
? sourceFieldName
|
||||
: destinationFieldName,
|
||||
valueFieldName
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
|
||||
private static createSelectableDataPoint(
|
||||
selectionId: ISelectionId,
|
||||
isSelected: boolean = false): SelectableDataPoint {
|
||||
|
@ -1048,6 +943,8 @@ export class SankeyDiagram implements IVisual {
|
|||
private parseSettings(dataView: DataView): SankeyDiagramSettings {
|
||||
let settings: SankeyDiagramSettings = SankeyDiagramSettings.parse<SankeyDiagramSettings>(dataView);
|
||||
|
||||
// settings.valueSourcesQuery = dataView.matrix.valueSources && dataView.matrix.valueSources[0].queryName;
|
||||
|
||||
// detect sorting chosen
|
||||
const foundSortedColumn = dataView.metadata.columns.find(col => col.sort !== undefined);
|
||||
if (foundSortedColumn) {
|
||||
|
@ -1057,7 +954,6 @@ export class SankeyDiagram implements IVisual {
|
|||
// change settings from high contrast mode
|
||||
settings.labels.fill = this.colorHelper.getHighContrastColor("foreground", settings.labels.fill);
|
||||
settings.linkLabels.fill = this.colorHelper.getHighContrastColor("foreground", settings.linkLabels.fill);
|
||||
|
||||
// node positions
|
||||
try {
|
||||
settings._nodePositions = <SankeyDiagramNodePositionSetting[]>JSON.parse(settings.nodeComplexSettings.nodePositions);
|
||||
|
@ -2025,7 +1921,7 @@ export class SankeyDiagram implements IVisual {
|
|||
})
|
||||
.style("font-size", this.dataView.settings.linkLabels.fontSize)
|
||||
.style("fill", this.dataView.settings.linkLabels.fill)
|
||||
.text((link: SankeyDiagramLink) =>
|
||||
.text((link: SankeyDiagramLink) => (link.label && (link.label.length > 0)) ? link.label :
|
||||
`${link.source.label.name || ""}-${link.destination.label.name || ""}:${(link.tooltipInfo[2] || { value: "" }).value}`
|
||||
);
|
||||
}
|
||||
|
@ -2401,7 +2297,7 @@ export class SankeyDiagram implements IVisual {
|
|||
this.enumerateLinks(instanceEnumeration);
|
||||
}
|
||||
|
||||
if(options.objectName === SankeyDiagram.NodesPropertyIdentifier.objectName) {
|
||||
if (options.objectName === SankeyDiagram.NodesPropertyIdentifier.objectName) {
|
||||
this.enumerateNodeCategories(instanceEnumeration);
|
||||
}
|
||||
|
||||
|
@ -2425,11 +2321,11 @@ export class SankeyDiagram implements IVisual {
|
|||
return !node.label.name.endsWith(SankeyDiagram.DuplicatedNamePostfix);
|
||||
}).forEach((node: SankeyDiagramNode) => {
|
||||
const identity: ISelectionId = <ISelectionId>node.identity,
|
||||
displayName: string = node.label.formattedName;
|
||||
displayName: string = node.label.formattedName;
|
||||
this.addAnInstanceToEnumeration(instanceEnumeration, {
|
||||
displayName,
|
||||
objectName: SankeyDiagram.NodesPropertyIdentifier.objectName,
|
||||
selector: identity.getSelector(),
|
||||
selector: ColorHelper.normalizeSelector(identity.getSelector(), false),
|
||||
properties: {
|
||||
fill: { solid: { color: node.fillColor } }
|
||||
}
|
||||
|
|
|
@ -30,13 +30,15 @@ import ISelectionId = powerbi.visuals.ISelectionId;
|
|||
import DataViewCategoryColumn = powerbi.DataViewCategoryColumn;
|
||||
import DataViewMatrix = powerbi.DataViewMatrix;
|
||||
import DataViewMatrixNode = powerbi.DataViewMatrixNode;
|
||||
|
||||
import ISelectionIdBuilder = powerbi.visuals.ISelectionIdBuilder;
|
||||
// powerbi.extensibility.visual
|
||||
import IVisualHost = powerbi.extensibility.visual.IVisualHost;
|
||||
|
||||
export class SelectionIdBuilder {
|
||||
private visualHost: IVisualHost;
|
||||
private matrix: DataViewMatrix;
|
||||
private matrixDataArray: DataViewMatrixNode[] = [];
|
||||
private builder: ISelectionIdBuilder;
|
||||
|
||||
constructor(
|
||||
IVisualHost: IVisualHost,
|
||||
|
@ -44,61 +46,22 @@ export class SelectionIdBuilder {
|
|||
|
||||
this.visualHost = IVisualHost;
|
||||
this.matrix = matrix
|
||||
this.getMatrixArray(this.matrix.rows.root);
|
||||
this.matrixDataArray.shift(); // delete matrix root node
|
||||
}
|
||||
|
||||
// walks the matrix deep first, adds each node to matrixDataArray
|
||||
private getMatrixArray(node: DataViewMatrixNode) {
|
||||
this.matrixDataArray.push(node);
|
||||
if (node.children) {
|
||||
node.children.forEach(child => this.getMatrixArray(child))
|
||||
}
|
||||
}
|
||||
|
||||
public createSelectionId(index: number): ISelectionId {
|
||||
let counter: number = 0,
|
||||
selectionId:ISelectionId;
|
||||
|
||||
this.matrix.rows.root.children.forEach((source: DataViewMatrixNode) => {
|
||||
if (counter == index){
|
||||
const categoryColumn: DataViewCategoryColumn = {
|
||||
source: {
|
||||
displayName: null,
|
||||
// tslint:disable-next-line: insecure-random
|
||||
queryName: `${Math.random()}-${+(new Date())}`
|
||||
},
|
||||
values: null,
|
||||
identity: [source.identity]
|
||||
};
|
||||
|
||||
selectionId = this.visualHost.createSelectionIdBuilder()
|
||||
.withCategory(categoryColumn,0)
|
||||
.createSelectionId();
|
||||
}
|
||||
counter+=1
|
||||
});
|
||||
|
||||
this.matrix.rows.root.children.forEach((source: powerbi.DataViewMatrixNode) =>{
|
||||
source.children.forEach((destination: powerbi.DataViewMatrixNode) => {
|
||||
if (counter == index){
|
||||
const categoryColumn1: DataViewCategoryColumn = {
|
||||
source: {
|
||||
displayName: null,
|
||||
// tslint:disable-next-line: insecure-random
|
||||
queryName: `${Math.random()}-${+(new Date())}`
|
||||
},
|
||||
values: null,
|
||||
identity: [source.identity]
|
||||
};
|
||||
const categoryColumn2: DataViewCategoryColumn = {
|
||||
source: {
|
||||
displayName: null,
|
||||
// tslint:disable-next-line: insecure-random
|
||||
queryName: `${Math.random()}-${+(new Date())}`
|
||||
},
|
||||
values: null,
|
||||
identity: [destination.identity]
|
||||
};
|
||||
selectionId = this.visualHost.createSelectionIdBuilder()
|
||||
.withCategory(categoryColumn1,0)
|
||||
.withCategory(categoryColumn2, 0)
|
||||
.createSelectionId();
|
||||
}
|
||||
counter += 1
|
||||
});
|
||||
});
|
||||
|
||||
return selectionId;
|
||||
debugger;
|
||||
return this.visualHost.createSelectionIdBuilder()
|
||||
.withMatrixNode(this.matrixDataArray[index], this.matrix.rows.levels)
|
||||
.createSelectionId();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,9 @@ import {
|
|||
|
||||
import DataViewObjectsParser = dataViewObjectsParser.DataViewObjectsParser;
|
||||
|
||||
import powerbi from "powerbi-visuals-api";
|
||||
import DataViewMetadataColumn = powerbi.DataViewMetadataColumn;
|
||||
|
||||
export enum CyclesDrawType {
|
||||
Duplicate,
|
||||
Backward
|
||||
|
@ -95,4 +98,5 @@ export class SankeyDiagramSettings extends DataViewObjectsParser {
|
|||
public cyclesLinks: SankeyNodeCycles = new SankeyNodeCycles();
|
||||
public _viewportSize: ViewportSize = {};
|
||||
public sort: string = "";
|
||||
public valueSourcesQuery: string = "";
|
||||
}
|
||||
|
|
1702
test/visualTest.ts
1702
test/visualTest.ts
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Загрузка…
Ссылка в новой задаче