From f65ab820c14431dc92a75f1dea652be07a4d126b Mon Sep 17 00:00:00 2001 From: chenwu Date: Wed, 19 Mar 2025 22:36:40 +0800 Subject: [PATCH] update1 --- __pycache__/dax_parser.cpython-313.pyc | Bin 2416 -> 2450 bytes __pycache__/model_connector.cpython-313.pyc | Bin 4085 -> 4238 bytes model_connector.py | 8 +- static/css/style.css | 130 +++++++++++++++++++- static/js/app.js | 71 +++++++++-- static/js/graph.js | 12 +- static/js/resize.js | 58 +++++++++ templates/index.html | 47 ++++++- 8 files changed, 307 insertions(+), 19 deletions(-) create mode 100644 static/js/resize.js diff --git a/__pycache__/dax_parser.cpython-313.pyc b/__pycache__/dax_parser.cpython-313.pyc index ee35d03d9dc32fc7d75011459266fded690d3883..9910f5d8adbf73faa1eede34d9144fe6b302db4e 100644 GIT binary patch delta 196 zcmew$G)b8IGcPX}0}!aP+}y|=$!M3DY!wq)oLW>I`NHye;hW&#-p1?)hY8Hhjs0}@ji zLK%e_!kCJg^cjkoCY!RVZH{GaVq)Am`2o8mQ(bfCcGVQK;Z z#SK87DL?`AFa@A7G#e@-{NZh$Eigo9`&XB@;Z`QW+PXNLVP3Q}Yo?W9JYDaYS;}mAVr#qX9cU*l0LX)N zE)o<7Z9vteeGovdoYPVgY;wHH#R)Wm#iD7ExhoYF%R=SJs->F6 z;wyms)WnOQ3(=653+=@mTn_H}16&#&oC$GRi2V4yaJ9FUI@t8N*c8*#d1|qD{(of8)nmT~`@uD}{rf&h`8xX#Lz)yP) S!Yup&n%nLKe&bEq=)>RRe&b~T delta 805 zcmah`&rcIU6yDh%-EQ06EkD8{X<>x~f=vrXK?EeBwFL~2LM7qQNNT%clkINvcAJ2S zi6$m`AS7nw;=y?G=!J{M6ZJ1J!9-UNCMI6IAcW}AnFaOeOZJ;@XWpCly>GV99``6m zvK(Saw7ht~{w(}h`8;;z>Uc44fMZ)Fb1hdj4X0SK%Z-|0xy6~PJKMNf%%|s)3)$(y z%5pZDnVz4YS)R_%;wkjz5=*Y)$al0#V24?anq)nRRoz5?cmb_2Ce8Juz~UQhl4A!s zyv;sk?$-QX@;|lsm|N=VC+YW? zgD9iLx56Jf^)2~J-{K>FKiIwdkl#QB>H&lac)VRVEKkaAl#RMuwJncxTo`0QriaLI zsA^pYg%(8UUdMu6G0co@Sw`8lAyqzMU3fY0M0iO?-oZnnhVO88F?*4rbd1}ZkkeYE z859U_%2GPTG&P^cLg|Fq?C0rDf}@xUW!kysn3hqwx2lk7-|Y=QXGaG}8&5E5b$1i~ zDdGKTp_d#2bq;(<{HY|O=a-y9V|YNhAN42k-J{O}LHtU|Mpa6wIY94+1eVo!poLNe z536xa7PP}|QJ6!At!+XY9iP*L9D){HQ5VGxQ_GDah?|b2Bb&9oB#j- diff --git a/model_connector.py b/model_connector.py index 811a327..07393f5 100644 --- a/model_connector.py +++ b/model_connector.py @@ -1,5 +1,5 @@ from sys import path -path.append('D:\\Personal_Files\\document\\GitHub\\PBI-MEASURE-CALLGRAPH\\MSNET') +path.append('C:\\Users\\ChenwuServise\\OneDrive - AZCollaboration\\Desktop\\GIT\\PBI-Measure-CallGraph\\MSNET') import pyadomd import pandas as pd @@ -18,8 +18,10 @@ class ModelConnector: """ self.server = server self.database = database - # self.connection_string = f"Provider=MSOLAP;Data Source={server}" - self.connection_string = f"Provider=MSOLAP;Data Source=localhost:56195" + if database == 'null' : + self.connection_string = f"Provider=MSOLAP;Data Source={server}" + else : + self.connection_string = f"Provider=MSOLAP;Data Source={server};Initial Catalog={database}" self.connection = None self.connect() diff --git a/static/css/style.css b/static/css/style.css index 3d4ed98..eed598f 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -37,7 +37,8 @@ h2 { /* Main content layout - new */ .main-content { display: flex; - gap: 10px; + gap: 0; + position: relative; } .visualization-container { @@ -47,6 +48,9 @@ h2 { box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); margin-bottom: 20px; flex: 4; + min-width: 60%; + max-width: 80%; + overflow: auto; } .measure-details { @@ -55,13 +59,36 @@ h2 { border-radius: 5px; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); flex: 1; - min-width: 30%; + min-width: 20%; max-width: 40%; height: fit-content; position: sticky; top: 20px; } +.resize-handle { + width: 8px; + background-color: #f0f0f0; + cursor: ew-resize; + transition: background-color 0.2s; + position: relative; + z-index: 10; +} + +.resize-handle:hover { + background-color: #0078d4; +} + +.resize-handle::after { + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: -4px; + right: -4px; + cursor: ew-resize; +} + /* Connection panel */ .connection-panel { background-color: white; @@ -119,13 +146,60 @@ button:hover { .controls { display: flex; - justify-content: space-between; + flex-direction: column; + gap: 10px; margin-bottom: 15px; } .search-box { - flex-grow: 1; - margin-left: 15px; + position: relative; + width: 100%; +} + +#reset-btn { + align-self: flex-start; +} + +.search-box input { + width: 100%; + padding: 8px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; +} + +.search-results { + position: absolute; + top: 100%; + left: 0; + right: 0; + background: white; + border: 1px solid #ddd; + border-radius: 4px; + margin-top: 4px; + max-height: 200px; + overflow-y: auto; + z-index: 1000; + display: none; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.search-results.active { + display: block; +} + +.search-result-item { + padding: 8px 12px; + cursor: pointer; + transition: background-color 0.2s; +} + +.search-result-item:hover { + background-color: #f5f5f5; +} + +.search-result-item.selected { + background-color: #e6f3ff; } #graph-container { @@ -361,3 +435,49 @@ button:hover { stroke: #d83b01; stroke-width: 3px; } + +.measure-controls { + margin: 10px 0; + display: flex; + gap: 10px; +} + +#format-btn { + background-color: #107c10; + padding: 8px 15px; + font-size: 0.9rem; + display: flex; + align-items: center; + gap: 5px; +} + +#format-btn:hover { + background-color: #0b5a0b; +} + +#format-btn:active { + transform: translateY(1px); +} + +.format-btn { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + background-color: #0078d4; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + transition: background-color 0.2s; +} + +.format-btn:hover { + background-color: #106ebe; +} + +.format-btn svg { + width: 16px; + height: 16px; +} diff --git a/static/js/app.js b/static/js/app.js index 21a1108..0fd94cf 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -10,11 +10,13 @@ const connectionStatus = document.getElementById('connection-status'); const resetBtn = document.getElementById('reset-btn'); const resetDetailBtn = document.getElementById('reset-detail-btn'); const searchInput = document.getElementById('search-input'); +const searchResults = document.getElementById('search-results'); const measureName = document.getElementById('measure-name'); const measureExpression = document.getElementById('measure-expression'); // Graph instance let graph = null; +let searchTimeout = null; // Event listeners document.addEventListener('DOMContentLoaded', () => { @@ -39,9 +41,12 @@ document.addEventListener('DOMContentLoaded', () => { }); // Search input - searchInput.addEventListener('input', (e) => { - if (graph) { - graph.searchNodes(e.target.value); + searchInput.addEventListener('input', handleSearch); + + // 点击其他地方时关闭搜索结果 + document.addEventListener('click', (e) => { + if (!searchInput.contains(e.target) && !searchResults.contains(e.target)) { + searchResults.classList.remove('active'); } }); }); @@ -51,11 +56,10 @@ document.addEventListener('DOMContentLoaded', () => { */ async function connectToModel() { const server = serverInput.value.trim() || 'localhost'; - const database = databaseInput.value.trim(); + const database = databaseInput.value.trim() || 'null'; - if (!database) { - showConnectionStatus('Please enter a database name', 'error'); - return; + if (!databaseInput.value.trim()) { + showConnectionStatus('No database name provided, using null', 'warning'); } try { @@ -123,6 +127,59 @@ function showMeasureDetails(measure) { measureExpression.textContent = measure.expression || 'No expression available'; } +// 处理搜索输入 +function handleSearch(e) { + const query = e.target.value.trim(); + + // 清除之前的定时器 + if (searchTimeout) { + clearTimeout(searchTimeout); + } + + // 设置新的定时器,避免频繁搜索 + searchTimeout = setTimeout(() => { + if (graph) { + const results = graph.searchNodes(query); + updateSearchResults(results); + } + }, 300); +} + +// 更新搜索结果下拉菜单 +function updateSearchResults(results) { + searchResults.innerHTML = ''; + + if (!results || results.length === 0) { + searchResults.classList.remove('active'); + return; + } + + results.forEach((node, index) => { + const div = document.createElement('div'); + div.className = 'search-result-item'; + div.textContent = node.id; + + // 添加点击事件 + div.addEventListener('click', () => { + searchInput.value = node.id; + searchResults.classList.remove('active'); + graph.selectNode(node); + graph.centerViewOnNode(node); + }); + + // 添加键盘导航 + div.addEventListener('keydown', (e) => { + if (e.key === 'Enter') { + div.click(); + } + }); + + searchResults.appendChild(div); + }); + + searchResults.classList.add('active'); +} + // Export functions for use in graph.js window.appFunctions = { showMeasureDetails diff --git a/static/js/graph.js b/static/js/graph.js index a8a612e..1beb999 100644 --- a/static/js/graph.js +++ b/static/js/graph.js @@ -658,6 +658,7 @@ class Graph { /** * Search for nodes by name * @param {string} query - The search query + * @returns {Array} - Array of matching nodes */ searchNodes(query) { // 移除之前的搜索匹配标记 @@ -666,20 +667,27 @@ class Graph { if (!query) { // 如果查询为空,恢复所有节点的正常显示 this.g.selectAll('.node').style('opacity', 1); - return; + return []; } const lowerQuery = query.toLowerCase(); + const matchingNodes = []; - // 标记匹配的节点 + // 标记匹配的节点并收集匹配结果 this.g.selectAll('.node') .each((d, i, nodes) => { const node = d3.select(nodes[i]); const isMatch = d.id.toLowerCase().includes(lowerQuery); + if (isMatch) { + matchingNodes.push(d); + } + // 为匹配的节点添加特殊类并使其文本加粗 node.classed('search-match', isMatch); }); + + return matchingNodes; } /** diff --git a/static/js/resize.js b/static/js/resize.js new file mode 100644 index 0000000..cb6cfa3 --- /dev/null +++ b/static/js/resize.js @@ -0,0 +1,58 @@ +document.addEventListener('DOMContentLoaded', function() { + const resizeHandle = document.querySelector('.resize-handle'); + const visualizationContainer = document.querySelector('.visualization-container'); + const measureDetails = document.querySelector('.measure-details'); + const mainContent = document.querySelector('.main-content'); + + let isResizing = false; + let startX; + let startWidth; + let startFlex; + + resizeHandle.addEventListener('mousedown', function(e) { + isResizing = true; + startX = e.pageX; + startWidth = visualizationContainer.offsetWidth; + startFlex = parseFloat(getComputedStyle(visualizationContainer).flex); + + document.body.style.cursor = 'ew-resize'; + document.body.style.userSelect = 'none'; + }); + + document.addEventListener('mousemove', function(e) { + if (!isResizing) return; + + const deltaX = e.pageX - startX; + const containerWidth = mainContent.offsetWidth; + const newWidth = startWidth + deltaX; + const widthPercent = (newWidth / containerWidth) * 100; + + // 确保宽度在限制范围内 + if (widthPercent >= 60 && widthPercent <= 80) { + visualizationContainer.style.flex = 'none'; + visualizationContainer.style.width = `${widthPercent}%`; + measureDetails.style.width = `${100 - widthPercent}%`; + } + }); + + document.addEventListener('mouseup', function() { + isResizing = false; + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + + // 保存当前宽度比例到 localStorage + const widthPercent = (visualizationContainer.offsetWidth / mainContent.offsetWidth) * 100; + localStorage.setItem('visualizationWidth', widthPercent); + }); + + // 恢复保存的宽度比例 + const savedWidth = localStorage.getItem('visualizationWidth'); + if (savedWidth) { + const widthPercent = parseFloat(savedWidth); + if (widthPercent >= 60 && widthPercent <= 80) { + visualizationContainer.style.flex = 'none'; + visualizationContainer.style.width = `${widthPercent}%`; + measureDetails.style.width = `${100 - widthPercent}%`; + } + } +}); \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 4a06070..2c5d5cd 100644 --- a/templates/index.html +++ b/templates/index.html @@ -7,6 +7,38 @@ +
@@ -35,7 +67,8 @@
@@ -85,10 +118,19 @@ });
- +

Measure Details

+
+ +

             
@@ -96,5 +138,6 @@ +