import { Component, OnInit } from '@angular/core'; import * as d3 from 'd3'; import {StateService} from '../state.service'; import * as PlotlyJS from 'plotly.js/dist/plotly.js'; @Component({ selector: 'app-state-management', templateUrl: './state-management.component.html', styleUrls: ['./state-management.component.css'] }) export class StateManagementComponent implements OnInit { private treeCreated = false; private selectedNode; private treemap; private svg; private hoveredNode = 1; public plot; constructor(private state: StateService) { } ngOnInit(): void { if (this.state.lshData) { this.createStateTree(); this.initializePlot(); } this.state.onNewLshData.subscribe(() => { if (!this.treeCreated) { this.createStateTree(); } else { this.update(); } this.initializePlot(); }); } initializePlot(): void { console.log('creating plot'); console.log(this.hoveredNode); console.log(this.state.states); const subplots = []; const data = []; this.state.selection.forEach((channelIndex: number, index: number) => { const channel = this.state.states[this.hoveredNode.toString()].state.queryWindow[channelIndex]; data.push({ x: [...Array(channel.length).keys()], y: channel, type: 'line', xaxis: 'x', yaxis: `y${index + 2}`, line: { color: this.state.colors[channelIndex], } }); subplots.push([`xy${index + 2}`]); }); const plot = { data: data, layout: { grid: { rows: this.state.selection.length, columns: 1, subplots: subplots, }, showlegend: false, hovermode: 'closest', autosize: true, margin: { l: 30, r: 30, t: 30, b: 0, pad: 4 }, height: 100 * this.state.selection.length, width: 300, titlefont: { size: 9 }, xaxis: { showgrid: false, zeroline: false, showticklabels: false, }, yaxis: { zeroline: false, showticklabels: false, } } }; this.state.selection.forEach((channel: number, index: number) => { plot.layout[`yaxis${index + 2}`] = { zeroline: false, showticklabels: false, annotations: [{ xref: 'paper', yref: 'paper', x: 0, xanchor: 'right', y: 1, yanchor: 'bottom', text: 'X axis label', showarrow: false }] // title: this.state.rawData[channel].name }; }); this.plot = plot; } createStateTree() { console.log('Creating tree!'); const margin = {top: 20, right: 10, bottom: 30, left: 10}; const width = 960 - margin.left - margin.right; const height = 500 - margin.top - margin.bottom; this.svg = d3.select('.state').append('svg') .attr("width", width + margin.right + margin.left) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); let root; const data = this.state.states; this.selectedNode = d3.hierarchy(data[1], function(d) { return d.children ? d.children.map(function(child) {return data[child]}) : undefined }); this.selectedNode.x0 = width / 8; this.selectedNode.y0 = 0; this.treeCreated = true; this.update(); } async update() { const margin = {top: 20, right: 90, bottom: 30, left: 90}; const width = 960 - margin.left - margin.right; const height = 500 - margin.top - margin.bottom; if (!this.treemap) { this.treemap = d3.tree().size([height, width]); } let i = 0; const duration = 750; const data = this.state.states; const currIndex = this.state.currStateIndex; this.hoveredNode = currIndex; const root = d3.hierarchy(data[1], function(d) { return d.children ? d.children.map(function(child) {return data[child]}) : undefined }); const source = this.selectedNode; const loadState = (id) => { this.state.loadState(id); }; const setHover = (id) => { this.hoveredNode = id; this.initializePlot(); }; // // Assigns the x and y position for the nodes // let root; // const data = this.state.states; // root = d3.hierarchy(data[1], function(d) { return d.children ? d.children.map(function(child) {return data[child]}) : undefined }); const treeData = this.treemap(root); // Compute the new tree layout. const nodes = treeData.descendants(); console.log('descendants'); console.log(nodes); const links = treeData.descendants().slice(1); // Normalize for fixed-depth. nodes.forEach(function(d) { d.y = d.depth * 180; }); // ****************** Nodes section *************************** this.svg.selectAll('g.node').select('circle').style("fill", "#fff"); // Update the nodes... const node = this.svg.selectAll('g.node') .data(nodes, function(d: any) { return d.id; }); // Enter any new modes at the parent's previous position. const nodeEnter = node.enter().append('g') .attr('class',function(d) { console.log('new nodes'); return 'node'; }) // .attr("transform", function(d) { // console.log('new nodes'); // return "translate(" + source.y0 + "," + source.x0 + ")"; // }) .on('click', function(d: any) { loadState(d.data.id); }); // Add Circle for the nodes nodeEnter.append('circle') .attr('class', 'node') .attr('r', 1e-6) .style("fill", function(d:any) { return d.data.id === currIndex ? "lightsteelblue" : "#fff"; }); // Add labels for the nodes nodeEnter.append('text') .attr("dy", ".35em") .attr("x", function(d:any) { return d.children ? -13 : 13; }) .attr("text-anchor", function(d:any) { return d.children ? "end" : "start"; }) .text(function(d: any) { return d.data.id; }); // // UPDATE const nodeUpdate = nodeEnter.merge(node as any); // Transition to the proper position for the node nodeUpdate.transition() .duration(duration) .attr("transform", function(d: any) { return "translate(" + d.x + "," + d.y + ")"; }); // Update the node attributes and style nodeUpdate.select('circle.node') .attr('r', 10) .style("fill", function(d:any) { return d.data.id === currIndex ? "lightsteelblue" : "#fff"; }) .attr('cursor', 'pointer') .on("mouseover", function(d) { d3.select(this).style("fill", function(d:any) { return "orange"; }); setHover(d.data.id); }) .on('mouseout', function(d) { d3.select(this).style("fill", function(d:any) { return d.data.id === currIndex ? "lightsteelblue" : "#fff"; }); }); // Remove any exiting nodes const nodeExit = node.exit().transition() .duration(duration) .attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; }) .remove(); // On exit reduce the node circles size to 0 nodeExit.select('circle') .attr('r', 1e-6); // On exit reduce the opacity of text labels nodeExit.select('text') .style('fill-opacity', 1e-6); // ****************** links section *************************** // Update the links... const link = this.svg.selectAll('path.link') .data(links, function(d: any) { return d.id; }); // // Enter any new links at the parent's previous position. const linkEnter = link.enter().insert('path', "g") .attr("class", "link") .attr('d', function(d) { const o = {x: source.x0, y: source.y0} return diagonal(o, o); }); // UPDATE const linkUpdate = linkEnter.merge(link as any); // Transition back to the parent element position linkUpdate.transition() .duration(duration) .attr('d', function(d: any) { return diagonal(d, d.parent) }); // Remove any exiting links const linkExit = link.exit().transition() .duration(duration) .attr('d', function(d) { var o = {x: source.x, y: source.y}; return diagonal(o, o); }) .remove(); // Store the old positions for transition. nodes.forEach(function(d: any) { d.x0 = d.x; d.y0 = d.y; }); // Creates a curved (diagonal) path from parent to the child nodes function diagonal(s, d) { const path = `M ${s.x} ${s.y} C ${(s.x + d.x) / 2} ${s.y}, ${(s.x + d.x) / 2} ${d.y}, ${d.x} ${d.y}`; return path; } // for (const state of Object.values(this.state.states)) { // const trace ={x:[3,9,8,10,4,6,5],y:[5,7,6,7,8,9,8],type:"scatter"}; // const trace1 ={x:[3,4,1,6,8,9,5],y:[4,2,5,2,1,7,3],type:"scatter"}; // const dataa = [trace, trace1]; // const layout = {title : "Simple JavaScript Graph"}; // const gd = await PlotlyJS.newPlot('graph', dataa, layout); // console.log(gd); // const plot = d3.select('.cartesianlayer').node(); // console.log(plot); // this.svg.node().appendChild(plot); // // const image = await PlotlyJS.toImage(gd,{format: 'svg', height:300,width:300}); // } } // Toggle children on click }