@kit-data-manager/visualization-component
Version:
The visualization-component is a dynamic, interactive graph component built using D3.js. It is designed to render graphs based on provided JSON data, making it ideal for visualizing complex relationships and networks in an intuitive manner.
396 lines (395 loc) • 16.1 kB
JavaScript
import { h } from "@stencil/core";
import * as d3 from "d3";
import { PrepareData } from "../../utils/dataUtil";
import { GraphSetup } from "../../utils/d3GraphSetup";
import { HandleEvents } from "../../utils/eventHandler";
/**
* VisualizationComponent is a custom web component that creates an interactive, force-directed graph
* using D3.js. It visualizes nodes and links based on provided JSON data.
*/
export class VisualizationComponent {
constructor() {
this.primaryNodeColor = '#008080';
this.size = '1350px,650px';
this.showAttributes = true;
this.showPrimaryLinks = true;
this.data = '[]';
this.excludeProperties = '';
this.showDetailsOnHover = true;
this.showLegend = true;
this.configurations = undefined;
}
/**
* Callback invoked when the 'data' property changes. Updates the visualization.
*
* @watch
* @param {string} newData - The new data.
*/
inputDataChanged(newData) {
// Update the visualization when the data is changed from outside the component.
try {
this.data = newData;
this.chartData = JSON.parse(newData);
this.generateD3Graph(this.chartData);
}
catch (error) {
console.error('Error in input data', error);
this.chartData = [];
}
}
/**
* Callback invoked when the 'showAttributes' property changes.
* Updates the data utility and regenerates the D3 graph.
*
* @watch
* @param {boolean} newValue - The new value of 'showAttributes'.
*/
showAttributesChanged(newValue) {
this.dataUtil = new PrepareData(this.showPrimaryLinks, newValue);
this.generateD3Graph(this.chartData);
}
/**
* Callback invoked when the 'showPrimaryLinks' property changes.
* Updates the data utility and regenerates the D3 graph.
*
* @watch
* @param {boolean} newValue - The new value of 'showPrimaryLinks'.
*/
showPrimaryLinksChanged(newValue) {
this.dataUtil = new PrepareData(newValue, this.showAttributes);
this.generateD3Graph(this.chartData);
}
/**
* Lifecycle method invoked when the component is connected.
* Initializes data utility, D3 graph setup, and parses input data.
*/
connectedCallback() {
this.dataUtil = new PrepareData(this.showPrimaryLinks, this.showAttributes);
this.d3GraphSetup = new GraphSetup(this.hostElement);
this.handleEvents = new HandleEvents(this.hostElement);
// Parse the input data when the component is initialized
try {
if (this.data != '') {
this.chartData = JSON.parse(this.data);
}
else {
this.chartData = [];
}
}
catch (error) {
console.error('Error parsing input data:', error);
this.chartData = [];
}
try {
if (this.configurations && Object.keys(this.configurations).length > 0) {
this.parsedConfig = JSON.parse(this.configurations); // Parse legendConfig
}
else {
this.parsedConfig = [];
}
}
catch (error) {
console.error('Error parsing legendConfig:', error);
this.parsedConfig = [];
}
}
/**
* Lifecycle method invoked when the component is loaded.
* Initializes and sets up the D3.js graph.
*/
componentDidLoad() {
try {
this.generateD3Graph(this.chartData);
}
catch (error) {
console.error('Error in input data', error);
}
}
/**
* Sets up the D3.js graph visualization based on the input data.
*
* @param {any[]} setupData - The data to set up the graph.
*/
generateD3Graph(setupData) {
var _a;
this.dataUtil.setShowAttributes(this.showAttributes);
// Prepare data
let defaultComponentData = Array.isArray(setupData) && setupData.length > 0 ? setupData : this.dataUtil.getDefaultComponentData();
const excludeProperties = this.excludeProperties.split(',');
let transformedData = this.dataUtil.transformData(defaultComponentData, excludeProperties);
// Set up color scale for links
const colorType = d3.scaleOrdinal(transformedData.links.map(d => d.relationType).sort(d3.ascending), d3.schemeCategory10);
const numPrimaryNodes = transformedData.nodes.filter(node => node.category === 'non_attribute').length;
// Initialize SVG and graph setup
const { svg, numericWidth, numericHeight } = this.d3GraphSetup.initializeSVG(numPrimaryNodes);
this.d3GraphSetup.clearSVG(svg);
// this.d3GraphSetup.createCustomMarkers(svg, transformedData.links, colorType);
const uniqueAttributeNames = Array.from(new Set(transformedData.nodes.filter(node => node.category === 'attribute').map(node => Object.keys(node)[1])));
const uniquePrimaryNodeNames = Array.from(new Set(transformedData.nodes.filter(node => node.category == 'non_attribute').map(node => Object.values(node)[0])));
const { attributeColorMap, attributeColorScale } = this.d3GraphSetup.attributeColorSetup(uniqueAttributeNames, this.parsedConfig);
this.d3GraphSetup.attributeColorSetup(uniquePrimaryNodeNames, this.parsedConfig);
this.tooltip = d3.select('body').append('div').attr('class', 'tooltip').style('opacity', 0).style('position', 'absolute').style('pointer-events', 'none');
const primaryNodeConfig = ((_a = this.parsedConfig[0]) === null || _a === void 0 ? void 0 : _a.primaryNodeConfigurations) || [];
this.d3GraphSetup.updateForceProperties({
center: {
x: 0.5, // Center position on the x-axis (0.5 for the middle of the SVG)
y: 0.5, // Center position on the y-axis (0.5 for the middle of the SVG)
},
charge: {
enabled: true,
strength: -5,
distanceMin: 0,
distanceMax: 2000,
},
link: {
distance: 90, // Adjust link distance as needed
},
});
// Create force simulation
const simulation = this.d3GraphSetup.createForceSimulation(transformedData.nodes, transformedData.links, numericWidth, numericHeight);
// Create links and nodes
const links = this.d3GraphSetup.createLinks(svg, transformedData.links, colorType);
const { nodesCreated, typeMatchedPrimaryNodes } = this.d3GraphSetup.createNodes(svg, transformedData.nodes, primaryNodeConfig, attributeColorMap, this.parsedConfig);
const { legendAttributesConfig, legendPrimaryConfig } = this.d3GraphSetup.prepareLegend(typeMatchedPrimaryNodes, uniqueAttributeNames, this.parsedConfig, attributeColorScale);
// Create the node legend
this.d3GraphSetup.createLegendNodes(svg, this.primaryNodeColor, this.showLegend, legendAttributesConfig, attributeColorMap, this.tooltip, legendPrimaryConfig);
// Apply event handlers
this.handleEvents.onClick(nodesCreated, links);
if (this.showDetailsOnHover)
this.handleEvents.applyMouseover(nodesCreated, links, this.tooltip);
this.handleEvents.applyDragToNodes(nodesCreated, simulation);
this.handleEvents.applyClickHandler();
// Apply simulation
this.d3GraphSetup.applySimulation(nodesCreated, links, simulation);
}
/**
* Renders the component with an SVG element for the graph.
*
* @return {JSX.Element}
*/
render() {
const [width, height] = this.size.split(',').map(s => s.trim());
return (h("div", { key: 'b5420e69dec1a74b8dfa95274cd58bad6b084afe' }, h("svg", { key: '897dfad61e7434b49a5c3f4aa71fe4f3670eccc1', id: "graph", style: { width: width, height: height } })));
}
static get is() { return "visualization-component"; }
static get encapsulation() { return "shadow"; }
static get originalStyleUrls() {
return {
"$": ["visualization-component.css"]
};
}
static get styleUrls() {
return {
"$": ["visualization-component.css"]
};
}
static get properties() {
return {
"size": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [{
"name": "prop",
"text": undefined
}, {
"name": "type",
"text": "{string}"
}],
"text": "The size of the graph. Defaults to '1350px,650px'."
},
"attribute": "size",
"reflect": false,
"defaultValue": "'1350px,650px'"
},
"showAttributes": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [{
"name": "prop",
"text": undefined
}, {
"name": "type",
"text": "{boolean}"
}],
"text": "Whether to show attributes in the graph. Defaults to true.\nIf true it will show all the attributes/properties\nIf false it wont show any attributes\nDefault value : true"
},
"attribute": "show-attributes",
"reflect": false,
"defaultValue": "true"
},
"showPrimaryLinks": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [{
"name": "prop",
"text": undefined
}, {
"name": "type",
"text": "{boolean}"
}],
"text": "Whether to show primary links in the graph.\nIf true it will show all the links between primary nodes\nDefaults to true."
},
"attribute": "show-primary-links",
"reflect": false,
"defaultValue": "true"
},
"data": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [{
"name": "prop",
"text": undefined
}, {
"name": "type",
"text": "{string}"
}],
"text": "Input data in JSON format for the graph."
},
"attribute": "data",
"reflect": false,
"defaultValue": "'[]'"
},
"excludeProperties": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [{
"name": "prop",
"text": undefined
}, {
"name": "type",
"text": "{string}"
}],
"text": "Properties to be excluded from outside the component. Defaults to an empty string."
},
"attribute": "exclude-properties",
"reflect": false,
"defaultValue": "''"
},
"showDetailsOnHover": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [{
"name": "prop",
"text": undefined
}, {
"name": "type",
"text": "{boolean}"
}],
"text": "Whether to show hover effects on the graph nodes. Defaults to true."
},
"attribute": "show-details-on-hover",
"reflect": false,
"defaultValue": "true"
},
"showLegend": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [{
"name": "prop",
"text": undefined
}, {
"name": "type",
"text": "{boolean}"
}],
"text": "Whether to show the legend in the graph. Defaults to true."
},
"attribute": "show-legend",
"reflect": false,
"defaultValue": "true"
},
"configurations": {
"type": "any",
"mutable": false,
"complexType": {
"original": "any",
"resolved": "any",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [{
"name": "prop",
"text": undefined
}, {
"name": "type",
"text": "{any}"
}],
"text": "The configuration object for customizing the graph color, and legend."
},
"attribute": "configurations",
"reflect": false
}
};
}
static get elementRef() { return "hostElement"; }
static get watchers() {
return [{
"propName": "data",
"methodName": "inputDataChanged"
}, {
"propName": "showAttributes",
"methodName": "showAttributesChanged"
}, {
"propName": "showPrimaryLinks",
"methodName": "showPrimaryLinksChanged"
}];
}
}
//# sourceMappingURL=visualization-component.js.map