Figure-Background color-Set

本文介绍了在MATLAB中设置图形窗口背景颜色的多种方法,包括使用默认设置、直接设置figure属性以及通过get和set函数调整当前figure的颜色。

MATLAB-note

Set the background color of figure

Way1

>>>set(0,'defaultfigurecolor','w');

way2

>>>figure('color','w');
>>>figure('color',[1 1 1]);
>>>set(gcf,'color','w');

转载于:https://my.oschina.net/u/3993524/blog/2886107

根据你给出的7c3s小区绘制代码,更改下面我给出的UE轨迹绘制代码,最后给我完整的代码 import dash from dash import dcc, html from dash.dependencies import Input, Output, State import plotly.graph_objects as go import json import pandas as pd import os import numpy as np from shapely.geometry import Point, Polygon # For point-in-polygon check # ---------------------------------------------------------------------- # 1. Initial setup and data structures # ---------------------------------------------------------------------- # Cell topology data (four vertex coordinates) # Note: Coordinates in order for drawing parallelogram cell_coords = { "cell0": [(0, 200), (115.4701, 200), (57.73503, 100), (-57.73503, 100)], "cell1": [(-57.73503, 100), (57.73503, 100), (115.4701, 0), (0, 0)], } # Define shapely polygons for point filtering CELL0_POLYGON = Polygon(cell_coords["cell0"]) CELL1_POLYGON = Polygon(cell_coords["cell1"]) # JSON file path JSON_FILE = "user_specific_parameter.json" # Initialize JSON file with required structure def initialize_json_file(null=None): """Create JSON file with required structure if not exists or empty, and reset trajectory_list if file exists""" if not os.path.exists(JSON_FILE) or os.stat(JSON_FILE).st_size == 0: default_data = { "ue_trajectories": { "trajectory_interval_second": 1, "interpolation_interval_second": 0.01, "trajectory_list": [] }, "ue_time_freq_granularity": { "ue_doppler_sampling_pattern": null, "ue_freq_sampling_pattern": null } } with open(JSON_FILE, 'w') as f: json.dump(default_data, f, indent=4) print(f"Initialized {JSON_FILE} with default structure") else: # File exists: reset trajectory_list to empty try: with open(JSON_FILE, 'r') as f: data = json.load(f) # Reset trajectory_list while preserving other settings if "ue_trajectories" in data: data["ue_trajectories"]["trajectory_list"] = [] else: data["ue_trajectories"] = { "trajectory_interval_second": 1, "interpolation_interval_second": 0.01, "trajectory_list": [] } with open(JSON_FILE, 'w') as f: json.dump(data, f, indent=4) print(f"Reset trajectory_list in existing {JSON_FILE}") except Exception as e: print(f"Error processing {JSON_FILE}: {str(e)}") # Reinitialize if error occurs default_data = { "ue_trajectories": { "trajectory_interval_second": 1, "interpolation_interval_second": 0.01, "trajectory_list": [] } } with open(JSON_FILE, 'w') as f: json.dump(default_data, f, indent=4) print(f"Reinitialized {JSON_FILE} due to error") # Initialize JSON file (will create or reset trajectory_list) initialize_json_file() # ---------------------------------------------------------------------- # 2. Background point generation functions (for click capture) # ---------------------------------------------------------------------- def generate_background_points(cell_polygons, density=5): """Generate uniformly distributed background points within specified polygons.""" # Determine boundary range min_x = min(min(c[0] for c in p.exterior.coords) for p in cell_polygons) - 10 max_x = max(max(c[0] for c in p.exterior.coords) for p in cell_polygons) + 10 min_y = min(min(c[1] for c in p.exterior.coords) for p in cell_polygons) - 10 max_y = max(max(c[1] for c in p.exterior.coords) for p in cell_polygons) + 10 # Create grid points x_coords = np.linspace(min_x, max_x, int((max_x - min_x) * density)) y_coords = np.linspace(min_y, max_y, int((max_y - min_y) * density)) points_x = [] points_y = [] # Filter points - keep only those inside any cell for x in x_coords: for y in y_coords: p = Point(x, y) if any(polygon.contains(p) or polygon.boundary.contains(p) for polygon in cell_polygons): points_x.append(x) points_y.append(y) return points_x, points_y # Generate background point data BG_POINTS_X, BG_POINTS_Y = generate_background_points([CELL0_POLYGON, CELL1_POLYGON], density=2) # Lower density for better performance # ---------------------------------------------------------------------- # 3. Cell topology plotting function # ---------------------------------------------------------------------- def create_initial_figure(all_trajectories_data=[]): """Plot initial cell topology (parallelograms).""" fig = go.Figure() # --- Plot cell0 and cell1 (topology) --- for i, (name, coords) in enumerate(cell_coords.items()): x, y = zip(*coords) fig.add_trace(go.Scatter( x=list(x) + [x[0]], y=list(y) + [y[0]], mode='lines', line=dict(color='green', width=2), name=f'{name} Topology', hoverinfo='skip', showlegend=True )) # --- Plot transparent background points (for click capture) --- fig.add_trace(go.Scatter( x=BG_POINTS_X, y=BG_POINTS_Y, mode='markers', marker=dict( size=5, color='rgba(0,0,0,0)', # Fully transparent opacity=0, line=dict(width=0) ), name='Click Area', hoverinfo='none', # Crucial: enable selection on this invisible trace customdata=[[x, y] for x, y in zip(BG_POINTS_X, BG_POINTS_Y)], unselected=dict(marker={'opacity': 0}), # Transparent when unselected selected=dict(marker={'color': 'rgba(255, 0, 0, 0.5)', 'opacity': 0.5, 'size': 8}), # Semi-transparent red when selected showlegend=False )) # Set chart layout fig.update_layout( title=f'Cell Topology & UE Trajectory Tool - Drawing UE #{len(all_trajectories_data) + 1}', xaxis_title='X Coordinate', yaxis_title='Y Coordinate', # Allow chart scaling xaxis=dict(range=[-80, 140]), yaxis=dict(range=[-20, 220]), # Key: set drag mode to lasso or rectangular selection dragmode='select', selectdirection='any', template='plotly_white', clickmode='event+select', # Ensure graph fills container autosize=True, ) return fig # ---------------------------------------------------------------------- # 4. Dash app layout # ---------------------------------------------------------------------- app = dash.Dash(__name__) app.layout = html.Div([ html.H1("UE Trajectory Mapping Tool"), # Graph area dcc.Graph( id='topology-graph', figure=create_initial_figure(), config={'displayModeBar': True, 'editable': True}, style={'height': '80vh', 'width': '100%'} # Fill most of page height ), # Coordinate display area html.Div(id='selected-data-output', style={'margin-top': '10px', 'font-size': '16px', 'font-weight': 'bold'}), # UE trajectory action buttons html.Div([ html.Button('Save Current UE Trajectory & Start Next', id='save-clear-button', n_clicks=0, style={'margin-right': '10px', 'padding': '10px', 'background-color': '#4CAF50', 'color': 'white', 'border': 'none', 'border-radius': '5px'}), html.Button('Export All Trajectories to JSON File', id='export-json-button', n_clicks=0, style={'padding': '10px', 'background-color': '#008CBA', 'color': 'white', 'border': 'none', 'border-radius': '5px'}), ], style={'margin-top': '20px'}), # Hidden storage components dcc.Store(id='current-ue-store', data=[]), dcc.Store(id='all-trajectories-store', data=json.load(open(JSON_FILE))["ue_trajectories"]["trajectory_list"]), dcc.Store(id='last-point-coords', data={'x': None, 'y': None}) ]) # ---------------------------------------------------------------------- # 5. Dash callback functions # ---------------------------------------------------------------------- # Callback 1: Handle mouse selection to update UE trajectory @app.callback( [Output('current-ue-store', 'data', allow_duplicate=True), Output('last-point-coords', 'data', allow_duplicate=True)], [Input('topology-graph', 'selectedData')], [State('current-ue-store', 'data')], prevent_initial_call=True ) def handle_graph_select(selectedData, current_ue_data): """Record new trajectory points when background points are selected.""" if selectedData is None or 'points' not in selectedData or not selectedData['points']: # No valid selection data return dash.no_update, dash.no_update # Get last selected point # Note: Plotly selection can choose multiple points. We take the last point as trajectory point. last_point = selectedData['points'][-1] # Confirm selection is from our 'Click Area' trace if last_point.get('curveNumber') != 2: # Assuming 'Click Area' is the third trace (index 2) return dash.no_update, dash.no_update x = last_point['x'] y = last_point['y'] z = 1.5 # Fixed z-coordinate # Construct new trajectory point: [x, y, z] new_point = [round(x, 4), round(y, 4), z] # Check if point is duplicate (prevent duplicates from repeated clicks) if current_ue_data and new_point == current_ue_data[-1]: return dash.no_update, dash.no_update # Update current trajectory list updated_trajectory = current_ue_data + [new_point] # Return updated data and latest click coordinates return updated_trajectory, {'x': round(x, 4), 'y': round(y, 4)} # Callback 2: Draw current UE trajectory and update coordinate display @app.callback( [Output('topology-graph', 'figure', allow_duplicate=True), Output('selected-data-output', 'children')], [Input('current-ue-store', 'data'), Input('last-point-coords', 'data')], [State('all-trajectories-store', 'data')], prevent_initial_call=True ) def update_graph_and_display(current_ue_data, last_point_coords, all_trajectories_data): """Draw trajectory points and lines based on current data, update coordinate display.""" # Recreate initial cell graph fig = create_initial_figure(all_trajectories_data) # Draw if current trajectory points exist if current_ue_data: # Extract x and y coordinates df = pd.DataFrame(current_ue_data, columns=['x', 'y', 'z']) # Draw lines and points fig.add_trace(go.Scatter( x=df['x'], y=df['y'], mode='lines+markers', marker=dict(size=8, color='red', symbol='circle'), # Clear circle points line=dict(color='red', width=2), name='UE Trajectory', hoverinfo='text', text=[f'({x}, {y})' for x, y in zip(df['x'], df['y'])], showlegend=True )) # Update coordinate display if last_point_coords['x'] is not None: display_text = f"Last Point Coordinates: X={last_point_coords['x']}, Y={last_point_coords['y']}, Z=1.5" else: display_text = "Drag/click on the chart area to draw UE trajectory..." return fig, display_text # Callback 3: Save current UE trajectory and start next @app.callback( [Output('current-ue-store', 'data', allow_duplicate=True), Output('all-trajectories-store', 'data', allow_duplicate=True)], [Input('save-clear-button', 'n_clicks')], [State('current-ue-store', 'data'), State('all-trajectories-store', 'data')], prevent_initial_call=True ) def save_and_clear_trajectory(n_clicks, current_ue_data, all_trajectories_data): """Save current UE trajectory and clear current trajectory list on button click.""" changed_id = [p['prop_id'] for p in dash.callback_context.triggered][0] if 'save-clear-button' in changed_id and n_clicks > 0: if not current_ue_data: print("Current UE trajectory is empty. Nothing saved.") return [], all_trajectories_data # 1. Save current trajectory updated_all_trajectories = all_trajectories_data + [current_ue_data] print(f"UE trajectory saved. Total trajectories: {len(updated_all_trajectories)}") # 2. Clear current trajectory data for next UE new_current_ue_data = [] return new_current_ue_data, updated_all_trajectories return dash.no_update, dash.no_update # Callback 4: Export all trajectories to JSON file @app.callback( Output('export-json-button', 'children'), [Input('export-json-button', 'n_clicks')], [State('all-trajectories-store', 'data')], prevent_initial_call=True ) def export_to_json(n_clicks, all_trajectories_data): """Export all saved trajectory data to JSON file.""" changed_id = [p['prop_id'] for p in dash.callback_context.triggered][0] if 'export-json-button' in changed_id and n_clicks > 0: try: with open(JSON_FILE, 'r') as f: data = json.load(f) data["ue_trajectories"]["trajectory_list"] = all_trajectories_data with open(JSON_FILE, 'w') as f: json.dump(data, f, indent=4) num_trajectories = len(all_trajectories_data) return f"Export successful! ({num_trajectories} trajectories)" except Exception as e: return "Export failed!" return 'Export All Trajectories to JSON File' if __name__ == '__main__': # Run application print(f"Please visit http://127.0.0.1:8050/") app.run(debug=True)
11-01
/** * Copyright (c) Tiny Technologies, Inc. All rights reserved. * Licensed under the LGPL or a commercial license. * For LGPL see License.txt in the project root for license information. * For commercial licenses see https://www.tiny.cloud/ */ body { background-color: #2f3742; color: #dfe0e4; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; line-height: 1.4; margin: 1rem; } a { color: #4099ff; } table { border-collapse: collapse; } /* Apply a default padding if legacy cellpadding attribute is missing */ table:not([cellpadding]) th, table:not([cellpadding]) td { padding: 0.4rem; } /* Set default table styles if a table has a positive border attribute and no inline css */ table[border]:not([border="0"]):not([style*="border-width"]) th, table[border]:not([border="0"]):not([style*="border-width"]) td { border-width: 1px; } /* Set default table styles if a table has a positive border attribute and no inline css */ table[border]:not([border="0"]):not([style*="border-style"]) th, table[border]:not([border="0"]):not([style*="border-style"]) td { border-style: solid; } /* Set default table styles if a table has a positive border attribute and no inline css */ table[border]:not([border="0"]):not([style*="border-color"]) th, table[border]:not([border="0"]):not([style*="border-color"]) td { border-color: #6d737b; } figure { display: table; margin: 1rem auto; } figure figcaption { color: #8a8f97; display: block; margin-top: 0.25rem; text-align: center; } hr { border-color: #6d737b; border-style: solid; border-width: 1px 0 0 0; } code { background-color: #6d737b; border-radius: 3px; padding: 0.1rem 0.2rem; } .mce-content-body:not([dir=rtl]) blockquote { border-left: 2px solid #6d737b; margin-left: 1.5rem; padding-left: 1rem; } .mce-content-body[dir=rtl] blockquote { border-right: 2px solid #6d737b; margin-right: 1.5rem; padding-right: 1rem; }
07-02
import dash from dash import dcc, html from dash.dependencies import Input, Output, State import plotly.graph_objects as go import json import pandas as pd import os import numpy as np import math from shapely.geometry import Point, Polygon from shapely.ops import unary_union # ---------------------------------------------------------------------- # 1. 配置参数读取 # ---------------------------------------------------------------------- CONFIG_FILE = "simulation_parameter_case.json" try: with open(CONFIG_FILE, 'r') as f: config = json.load(f) isd = config["channel_parameters"]["isd"] min_bs_ut_dist = config["channel_parameters"].get("min_bs_ut_dist", 10) # 获取禁区半径 display_cell_ids = [bs["cell_id"] for bs in config["bs_parameters"]] except Exception as e: print(f"Error: {e} - Using default values") isd = 200 min_bs_ut_dist = 10 # 默认禁区半径 display_cell_ids = [] # ---------------------------------------------------------------------- # 2. 7c3s小区拓扑定义(添加精确四边形扇区) # ---------------------------------------------------------------------- class CellTopology: def __init__(self, isd=200): self.isd = isd self.radius = (self.isd / 2.0) / (np.cos(np.deg2rad(30))) # 基站位置 self.bs_loc_set = np.array([ [0.0, 0.0], # 中心基站 [-math.sqrt(3) * self.isd / 2.0, self.isd / 2.0], # 左上 [0.0, self.isd], # 上 [math.sqrt(3) * self.isd / 2.0, self.isd / 2.0], # 右上 [math.sqrt(3) * self.isd / 2.0, -self.isd / 2.0], # 右下 [0.0, -self.isd], # 下 [-math.sqrt(3) * self.isd / 2.0, -self.isd / 2.0] # 左下 ]) # 六边形顶点 self.center_bs_hexgon_vertex = np.array([ [self.radius, 0.0], [self.radius / 2.0, self.isd / 2.0], [-self.radius / 2.0, self.isd / 2.0], [-self.radius, 0.0], [-self.radius / 2.0, -self.isd / 2.0], [self.radius / 2.0, -self.isd / 2.0], [self.radius, 0.0] ]) # 扇区分割线 self.center_sector_split_line = np.array([ [0.0, 0.0], [self.radius, 0.0], # 0° [0.0, 0.0], [-self.radius / 2.0, self.isd / 2.0], # 120° [0.0, 0.0], [-self.radius / 2.0, -self.isd / 2.0] # 240° ]) # 存储每个扇区的四边形多边形 self.sector_polygons = {} current_cell_id = 0 # 扇区角度定义 sector_angles = [0, 120, 240] for bs_id in range(7): bs_loc = self.bs_loc_set[bs_id] hex_vertices = self.center_bs_hexgon_vertex + bs_loc # 为每个扇区创建四边形多边形 for i, angle in enumerate(sector_angles): # 扇区起始点和结束点 start_idx = i end_idx = (i + 1) % 3 # 扇区四边形顶点:基站中心 + 三个六边形顶点 quad_points = np.array([ bs_loc, # 基站中心 hex_vertices[start_idx * 2], hex_vertices[start_idx * 2 + 1], hex_vertices[end_idx * 2] ]) self.sector_polygons[current_cell_id] = quad_points current_cell_id += 1 # 使用配置文件中的ISD值初始化拓扑 topology = CellTopology(isd=isd) # ---------------------------------------------------------------------- # 3. UE轨迹工具初始设置 # ---------------------------------------------------------------------- JSON_FILE = "user_specific_parameter.json" # 初始化JSON文件 def initialize_json_file(): if not os.path.exists(JSON_FILE) or os.stat(JSON_FILE).st_size == 0: default_data = { "ue_trajectories": { "trajectory_interval_second": 1, "interpolation_interval_second": 0.01, "trajectory_list": [], "isd": isd, # 添加ISD信息 "min_bs_ut_dist": min_bs_ut_dist, # 添加禁区半径 "display_cell_ids": display_cell_ids # 添加显示的cell_id信息 } } with open(JSON_FILE, 'w') as f: json.dump(default_data, f, indent=4) else: try: with open(JSON_FILE, 'r') as f: data = json.load(f) if "ue_trajectories" in data: # 更新当前ISD和显示的cell_id data["ue_trajectories"]["isd"] = isd data["ue_trajectories"]["min_bs_ut_dist"] = min_bs_ut_dist data["ue_trajectories"]["display_cell_ids"] = display_cell_ids data["ue_trajectories"]["trajectory_list"] = [] else: data["ue_trajectories"] = { "trajectory_interval_second": 1, "interpolation_interval_second": 0.01, "trajectory_list": [], "isd": isd, "min_bs_ut_dist": min_bs_ut_dist, "display_cell_ids": display_cell_ids } with open(JSON_FILE, 'w') as f: json.dump(data, f, indent=4) except Exception as e: print(f"初始化JSON文件错误: {e}") default_data = { "ue_trajectories": { "trajectory_interval_second": 1, "interpolation_interval_second": 0.01, "trajectory_list": [], "isd": isd, "min_bs_ut_dist": min_bs_ut_dist, "display_cell_ids": display_cell_ids } } with open(JSON_FILE, 'w') as f: json.dump(default_data, f, indent=4) # 初始化JSON文件 initialize_json_file() # ---------------------------------------------------------------------- # 4. 背景点生成函数(只生成指定扇区内的点,避开禁区圆) # ---------------------------------------------------------------------- def generate_background_points(display_cell_ids, min_bs_ut_dist, density=5): """只为显示的扇区生成背景点,避开基站禁区""" display_polygons = [] forbidden_circles = [] # 存储禁区圆对象 # 获取所有要显示扇区的多边形 for cell_id in display_cell_ids: if cell_id in topology.sector_polygons: quad_points = topology.sector_polygons[cell_id] poly = Polygon(quad_points) display_polygons.append(poly) # 创建对应基站的禁区圆 # 计算基站索引(每个基站3个扇区) bs_index = cell_id // 3 if bs_index < len(topology.bs_loc_set): center = topology.bs_loc_set[bs_index] circle = Point(center).buffer(min_bs_ut_dist) forbidden_circles.append(circle) if not display_polygons: return [], [] # 合并所有多边形 combined_poly = unary_union(display_polygons) # 合并所有禁区圆 forbidden_area = unary_union(forbidden_circles) if forbidden_circles else None # 计算边界 min_x, min_y, max_x, max_y = combined_poly.bounds min_x -= 10 max_x += 10 min_y -= 10 max_y += 10 # 生成网格点 x_coords = np.linspace(min_x, max_x, int((max_x - min_x) * density)) y_coords = np.linspace(min_y, max_y, int((max_y - min_y) * density)) points_x = [] points_y = [] # 筛选在合并多边形内且不在禁区内的点 for x in x_coords: for y in y_coords: p = Point(x, y) if combined_poly.contains(p): # 检查是否在禁区内 in_forbidden = False if forbidden_area: in_forbidden = forbidden_area.contains(p) # 额外检查单独圆(解决边界问题) if not in_forbidden: for circle in forbidden_circles: if circle.contains(p): in_forbidden = True break if not in_forbidden: points_x.append(x) points_y.append(y) return points_x, points_y # 生成指定扇区的背景点 BG_POINTS_X, BG_POINTS_Y = generate_background_points( display_cell_ids, min_bs_ut_dist, density=1 ) # ---------------------------------------------------------------------- # 5. 小区拓扑绘图函数(只绘制指定四边形扇区) # ---------------------------------------------------------------------- def create_initial_figure(all_trajectories_data=[]): fig = go.Figure() # 收集所有要显示的扇区顶点坐标 all_x = [] all_y = [] # 1. 只绘制指定的四边形扇区 for cell_id in display_cell_ids: if cell_id in topology.sector_polygons: quad_points = topology.sector_polygons[cell_id] all_x.extend(quad_points[:, 0]) all_y.extend(quad_points[:, 1]) # 闭合多边形(第一个点添加到末尾) x_quad = list(quad_points[:, 0]) + [quad_points[0, 0]] y_quad = list(quad_points[:, 1]) + [quad_points[0, 1]] # 添加四边形扇区边界 fig.add_trace(go.Scatter( x=x_quad, y=y_quad, mode='lines', line=dict(color='green', width=2), name=f'Cell {cell_id}', hoverinfo='skip', showlegend=False )) # 2. 添加扇区ID标签 labels_x = [] labels_y = [] labels_text = [] for cell_id in display_cell_ids: if cell_id in topology.sector_polygons: quad_points = topology.sector_polygons[cell_id] # 标签位置:四边形中心点 center_x = np.mean(quad_points[:, 0]) center_y = np.mean(quad_points[:, 1]) labels_x.append(center_x) labels_y.append(center_y) labels_text.append(f"Cell {cell_id}") # 添加中心点到坐标集合 all_x.append(center_x) all_y.append(center_y) fig.add_trace(go.Scatter( x=labels_x, y=labels_y, mode='text', text=labels_text, textfont=dict(size=12, color='black'), textposition='middle center', hoverinfo='skip', showlegend=False )) # 3. 添加透明背景点 fig.add_trace(go.Scatter( x=BG_POINTS_X, y=BG_POINTS_Y, mode='markers', marker=dict(size=5, color='rgba(0,0,0,0)', opacity=0), name='Click Area', hoverinfo='none', customdata=[[x, y] for x, y in zip(BG_POINTS_X, BG_POINTS_Y)], unselected=dict(marker={'opacity': 0}), selected=dict(marker={'color': 'rgba(255, 0, 0, 0.5)', 'opacity': 0.5, 'size': 8}), showlegend=False )) # 添加背景点到坐标集合 all_x.extend(BG_POINTS_X) all_y.extend(BG_POINTS_Y) # 4. 绘制禁区圆 if min_bs_ut_dist > 0: for bs_id, bs_loc in enumerate(topology.bs_loc_set): # 检查该基站是否有扇区被显示(优化性能) base_has_displayed_cell = any( cell_id // 3 == bs_id for cell_id in display_cell_ids ) if not base_has_displayed_cell: continue # 生成圆上的点(100个点构成圆) theta = np.linspace(0, 2 * np.pi, 100) circle_x = bs_loc[0] + min_bs_ut_dist * np.cos(theta) circle_y = bs_loc[1] + min_bs_ut_dist * np.sin(theta) # 添加圆轨迹 fig.add_trace(go.Scatter( x=circle_x.tolist() + [circle_x[0]], # 闭合圆形 y=circle_y.tolist() + [circle_y[0]], mode='lines', line=dict(color='red', width=1.5, dash='dot'), name=f'BS {bs_id} Forbidden Zone', hoverinfo='skip', showlegend=False )) # 添加圆点到坐标集合 all_x.extend(circle_x) all_y.extend(circle_y) # 5. 自适应计算坐标轴范围 if all_x and all_y: min_x = min(all_x) max_x = max(all_x) min_y = min(all_y) max_y = max(all_y) # 添加10%的额外边距 x_margin = (max_x - min_x) * 0.1 y_margin = (max_y - min_y) * 0.1 # 防止边距为0 if x_margin == 0: x_margin = 10 if y_margin == 0: y_margin = 10 x_range = [min_x - x_margin, max_x + x_margin] y_range = [min_y - y_margin, max_y + y_margin] else: # 默认范围 x_range = [-100, 100] y_range = [-100, 100] # 关键修改:移除比例锁定 fig.update_layout( title=dict( text=f'Selected Cells: {display_cell_ids} - UE #{len(all_trajectories_data) + 1} | Forbidden Radius: {min_bs_ut_dist}m', x=0.02, y=0.98, xanchor='left', yanchor='top', font=dict(size=12) ), xaxis_title='X Coordinate (m)', yaxis_title='Y Coordinate (m)', xaxis=dict( range=x_range, constrain='domain', automargin=True # 自动调整边距 ), yaxis=dict( range=y_range, constrain='domain', automargin=True # 自动调整边距 ), dragmode='select', template='plotly_white', clickmode='event+select', margin=dict(l=0, r=0, t=30, b=0), height=800 ) return fig # ---------------------------------------------------------------------- # 6. Dash应用布局 # ---------------------------------------------------------------------- app = dash.Dash(__name__) app.layout = html.Div([ html.H1(f"UE Trajectory Mapping Tool (ISD={isd}m)"), html.Div([ html.P(f"Displayed Cells: {display_cell_ids} | Forbidden Radius: {min_bs_ut_dist}m", style={'font-weight': 'bold', 'margin-top': '10px'}) ]), dcc.Graph( id='topology-graph', figure=create_initial_figure(), config={'displayModeBar': True}, style={'height': '80vh', 'width': '100%'} ), html.Div(id='selected-data-output'), html.Div([ html.Button('Save Current UE Trajectory', id='save-clear-button', n_clicks=0, style={'margin-right': '10px', 'padding': '10px', 'background-color': '#4CAF50', 'color': 'white'}), html.Button('Export to JSON', id='export-json-button', n_clicks=0, style={'padding': '10px', 'background-color': '#008CBA', 'color': 'white'}), ], style={'margin': '20px 0'}), dcc.Store(id='current-ue-store', data=[]), dcc.Store(id='all-trajectories-store', data=[]), dcc.Store(id='last-point-coords', data={'x': None, 'y': None}) ]) # ---------------------------------------------------------------------- # 7. Dash回调函数 # ---------------------------------------------------------------------- @app.callback( [Output('current-ue-store', 'data', allow_duplicate=True), Output('last-point-coords', 'data', allow_duplicate=True)], [Input('topology-graph', 'selectedData')], [State('current-ue-store', 'data'), State('topology-graph', 'figure')], # 添加对图形状态的依赖 prevent_initial_call=True ) def handle_graph_select(selectedData, current_ue_data, figure): if selectedData is None or not selectedData['points']: return dash.no_update, dash.no_update last_point = selectedData['points'][-1] # 动态计算背景点轨迹的曲线编号 background_curve_number = len(display_cell_ids) + 1 # 四边形扇区 + 标签轨迹 # 确认选择来自"点击区域"轨迹 if last_point.get('curveNumber') != background_curve_number: return dash.no_update, dash.no_update x = last_point['x'] y = last_point['y'] z = 1.5 new_point = [round(x, 4), round(y, 4), z] if current_ue_data and new_point == current_ue_data[-1]: return dash.no_update, dash.no_update updated_trajectory = current_ue_data + [new_point] return updated_trajectory, {'x': round(x, 4), 'y': round(y, 4)} @app.callback( [Output('topology-graph', 'figure', allow_duplicate=True), Output('selected-data-output', 'children')], [Input('current-ue-store', 'data'), Input('last-point-coords', 'data')], [State('all-trajectories-store', 'data')], prevent_initial_call=True ) def update_graph_and_display(current_ue_data, last_point_coords, all_trajectories_data): fig = create_initial_figure(all_trajectories_data) if current_ue_data: df = pd.DataFrame(current_ue_data, columns=['x', 'y', 'z']) fig.add_trace(go.Scatter( x=df['x'], y=df['y'], mode='lines+markers', marker=dict(size=8, color='red', symbol='circle'), line=dict(color='red', width=2), name='UE Trajectory', hoverinfo='text', text=[f'({x}, {y})' for x, y in zip(df['x'], df['y'])], showlegend=True )) if last_point_coords['x'] is not None: display_text = f"Last Point: X={last_point_coords['x']}, Y={last_point_coords['y']}, Height=1.5m" else: display_text = "Drag/click on the chart area to draw UE trajectory..." return fig, display_text @app.callback( [Output('current-ue-store', 'data', allow_duplicate=True), Output('all-trajectories-store', 'data', allow_duplicate=True)], [Input('save-clear-button', 'n_clicks')], [State('current-ue-store', 'data'), State('all-trajectories-store', 'data')], prevent_initial_call=True ) def save_and_clear_trajectory(n_clicks, current_ue_data, all_trajectories_data): if n_clicks > 0: if not current_ue_data: return [], all_trajectories_data updated_all_trajectories = all_trajectories_data + [current_ue_data] new_current_ue_data = [] return new_current_ue_data, updated_all_trajectories return dash.no_update, dash.no_update @app.callback( Output('export-json-button', 'children'), [Input('export-json-button', 'n_clicks')], [State('all-trajectories-store', 'data')], prevent_initial_call=True ) def export_to_json(n_clicks, all_trajectories_data): if n_clicks > 0: try: with open(JSON_FILE, 'r') as f: data = json.load(f) data["ue_trajectories"]["trajectory_list"] = all_trajectories_data with open(JSON_FILE, 'w') as f: json.dump(data, f, indent=4) num_trajectories = len(all_trajectories_data) return f"Exported {num_trajectories} trajectories to {JSON_FILE}" except Exception: return "Export failed!" return 'Export All Trajectories to JSON File' # ---------------------------------------------------------------------- # 7. 运行应用 # ---------------------------------------------------------------------- if __name__ == '__main__': print(f"Please visit http://127.0.0.1:8050/") app.run(debug=True) 将上面代码中的中文注释翻译为英文
最新发布
11-04
import dash from dash import dcc, html from dash.dependencies import Input, Output, State import plotly.graph_objects as go import json import pandas as pd import os import numpy as np from shapely.geometry import Point, Polygon # 用于点是否在多边形内的判断 # ---------------------------------------------------------------------- # 1. 初始设置和数据结构 # ---------------------------------------------------------------------- # 小区拓扑数据 (四个顶点坐标) # 注意:坐标按顺序排列,用于绘制平行四边形 cell_coords = { "cell1": [(0, 200), (115.4701, 200), (57.73503, 100), (-57.73503, 100)], "cell2": [(-57.73503, 100), (57.73503, 100), (115.4701, 0), (0, 0)], } # 定义shapely多边形用于点过滤 CELL1_POLYGON = Polygon(cell_coords["cell1"]) CELL2_POLYGON = Polygon(cell_coords["cell2"]) # JSON 文件路径 JSON_FILE = "user_specific_parameter.json" # 初始化JSON文件结构(如果不存在或为空) def initialize_json_file(): if not os.path.exists(JSON_FILE) or os.stat(JSON_FILE).st_size == 0: initial_data = {"ue_trajectories": {"trajectory_list": []}} with open(JSON_FILE, 'w') as f: json.dump(initial_data, f, indent=4) print(f"Initialized {JSON_FILE}") # 初始化JSON文件 initialize_json_file() # ---------------------------------------------------------------------- # 2. 背景点生成函数 (用于捕获点击) # ---------------------------------------------------------------------- def generate_background_points(cell_polygons, density=5): """在指定多边形区域内生成均匀分布的背景点.""" # 确定边界范围 min_x = min(min(c[0] for c in p.exterior.coords) for p in cell_polygons) - 10 max_x = max(max(c[0] for c in p.exterior.coords) for p in cell_polygons) + 10 min_y = min(min(c[1] for c in p.exterior.coords) for p in cell_polygons) - 10 max_y = max(max(c[1] for c in p.exterior.coords) for p in cell_polygons) + 10 # 创建网格点 x_coords = np.linspace(min_x, max_x, int((max_x - min_x) * density)) y_coords = np.linspace(min_y, max_y, int((max_y - min_y) * density)) points_x = [] points_y = [] # 过滤点,只保留在任意小区内的点 for x in x_coords: for y in y_coords: p = Point(x, y) if any(polygon.contains(p) or polygon.boundary.contains(p) for polygon in cell_polygons): points_x.append(x) points_y.append(y) return points_x, points_y # 生成背景点数据 BG_POINTS_X, BG_POINTS_Y = generate_background_points([CELL1_POLYGON, CELL2_POLYGON], density=2) # 降低密度以提高性能 # ---------------------------------------------------------------------- # 3. 小区拓扑图绘制函数 # ---------------------------------------------------------------------- def create_initial_figure(all_trajectories_data=[]): """绘制初始的小区拓扑图(平行四边形).""" fig = go.Figure() # --- 绘制小区1 和 小区2 (拓扑) --- for i, (name, coords) in enumerate(cell_coords.items()): x, y = zip(*coords) fig.add_trace(go.Scatter( x=list(x) + [x[0]], y=list(y) + [y[0]], mode='lines', line=dict(color='green', width=2), name=f'{name} Topology', hoverinfo='skip', showlegend=True )) # --- 绘制透明背景点 (用于点击捕获) --- fig.add_trace(go.Scatter( x=BG_POINTS_X, y=BG_POINTS_Y, mode='markers', marker=dict( size=5, color='rgba(0,0,0,0)', # 完全透明 opacity=0, line=dict(width=0) ), name='Click Area', hoverinfo='none', # crucial: enable selection on this invisible trace customdata=[[x, y] for x, y in zip(BG_POINTS_X, BG_POINTS_Y)], unselected=dict(marker={'opacity': 0}), # 未选择时透明 selected=dict(marker={'color': 'rgba(255, 0, 0, 0.5)', 'opacity': 0.5, 'size': 8}), # 选择时半透明红色 showlegend=False )) # 设置图表布局 fig.update_layout( title=f'小区拓扑图与UE轨迹绘制工具 - 正在绘制第 {len(all_trajectories_data) + 1} 个UE轨迹', xaxis_title='X 坐标', yaxis_title='Y 坐标', # 移除比例约束,允许图表拉伸 xaxis=dict(range=[-80, 140]), yaxis=dict(range=[-20, 220]), # 关键: 设置拖拽模式为 lasso 或 rectangular 选择 dragmode='select', selectdirection='any', template='plotly_white', clickmode='event+select', # 确保图形占满整个容器 autosize=True, ) return fig # ---------------------------------------------------------------------- # 4. Dash 应用和布局 # ---------------------------------------------------------------------- app = dash.Dash(__name__) app.layout = html.Div([ html.H1("UE 轨迹绘制工具"), # 图表区域: 设置 style 确保图表高度较大,占满页面 dcc.Graph( id='topology-graph', figure=create_initial_figure(), config={'displayModeBar': True, 'editable': True}, style={'height': '80vh', 'width': '100%'} # 确保图形占满大部分页面高度 ), # 坐标显示区域 html.Div(id='selected-data-output', style={'margin-top': '10px', 'font-size': '16px', 'font-weight': 'bold'}), # UE 轨迹操作按钮 html.Div([ html.Button('保存当前UE轨迹并开始下一个', id='save-clear-button', n_clicks=0, style={'margin-right': '10px', 'padding': '10px', 'background-color': '#4CAF50', 'color': 'white', 'border': 'none', 'border-radius': '5px'}), html.Button('导出所有轨迹到JSON文件', id='export-json-button', n_clicks=0, style={'padding': '10px', 'background-color': '#008CBA', 'color': 'white', 'border': 'none', 'border-radius': '5px'}), ], style={'margin-top': '20px'}), # 隐藏的存储组件 dcc.Store(id='current-ue-store', data=[]), dcc.Store(id='all-trajectories-store', data=json.load(open(JSON_FILE))["ue_trajectories"]["trajectory_list"]), dcc.Store(id='last-point-coords', data={'x': None, 'y': None}) ]) # ---------------------------------------------------------------------- # 5. Dash 回调函数 # ---------------------------------------------------------------------- # 回调1: 处理鼠标选择事件,更新当前UE轨迹 @app.callback( [Output('current-ue-store', 'data', allow_duplicate=True), Output('last-point-coords', 'data', allow_duplicate=True)], [Input('topology-graph', 'selectedData')], [State('current-ue-store', 'data')], prevent_initial_call=True ) def handle_graph_select(selectedData, current_ue_data): """选择背景点时,记录新的轨迹点.""" if selectedData is None or 'points' not in selectedData or not selectedData['points']: # 没有有效的选择数据 return dash.no_update, dash.no_update # 获取最后一个被选择的点 # 注意: Plotly selection 可以选择多个点。我们只取最后一个点作为轨迹点。 last_point = selectedData['points'][-1] # 确认选择的是我们用于捕获的 'Click Area' 轨迹 if last_point.get('curveNumber') != 2: # 假设 'Click Area' 是第三个轨迹 (索引2) return dash.no_update, dash.no_update x = last_point['x'] y = last_point['y'] z = 1.5 # 固定z坐标 # 构造新的轨迹点: [x, y, z] new_point = [round(x, 4), round(y, 4), z] # 检查是否重复添加了同一个点 (防止多次点击同一区域导致的重复) if current_ue_data and new_point == current_ue_data[-1]: return dash.no_update, dash.no_update # 更新当前轨迹列表 updated_trajectory = current_ue_data + [new_point] # 返回更新后的数据和最新的点击坐标 return updated_trajectory, {'x': round(x, 4), 'y': round(y, 4)} # 回调2: 绘制当前UE轨迹和更新坐标显示 @app.callback( [Output('topology-graph', 'figure', allow_duplicate=True), Output('selected-data-output', 'children')], [Input('current-ue-store', 'data'), Input('last-point-coords', 'data')], [State('all-trajectories-store', 'data')], prevent_initial_call=True ) def update_graph_and_display(current_ue_data, last_point_coords, all_trajectories_data): """根据当前轨迹数据,在图上绘制轨迹点和连线,并更新坐标显示.""" # 重新创建初始的小区图 fig = create_initial_figure(all_trajectories_data) # 如果当前有轨迹点,则进行绘制 if current_ue_data: # 提取 x 和 y 坐标 df = pd.DataFrame(current_ue_data, columns=['x', 'y', 'z']) # 绘制连线和点 fig.add_trace(go.Scatter( x=df['x'], y=df['y'], mode='lines+markers', marker=dict(size=8, color='red', symbol='circle'), # 使用圆点更清晰 line=dict(color='red', width=2), name='UE Trajectory', hoverinfo='text', text=[f'({x}, {y})' for x, y in zip(df['x'], df['y'])], showlegend=True )) # 强制更新标题 (在 create_initial_figure 中已更新) # 更新坐标显示 if last_point_coords['x'] is not None: display_text = f"上一次轨迹点坐标: X={last_point_coords['x']}, Y={last_point_coords['y']}, Z=1.5" else: display_text = "请使用鼠标拖拽/点击图表上的区域来绘制UE轨迹..." return fig, display_text # 回调3: 保存当前UE轨迹并开始下一个 @app.callback( [Output('current-ue-store', 'data', allow_duplicate=True), Output('all-trajectories-store', 'data', allow_duplicate=True)], [Input('save-clear-button', 'n_clicks')], [State('current-ue-store', 'data'), State('all-trajectories-store', 'data')], prevent_initial_call=True ) def save_and_clear_trajectory(n_clicks, current_ue_data, all_trajectories_data): """点击按钮时,保存当前UE轨迹,并清空当前轨迹列表.""" changed_id = [p['prop_id'] for p in dash.callback_context.triggered][0] if 'save-clear-button' in changed_id and n_clicks > 0: if not current_ue_data: print("当前UE轨迹为空,未保存任何数据。") return [], all_trajectories_data # 1. 保存当前轨迹 updated_all_trajectories = all_trajectories_data + [current_ue_data] print(f"UE轨迹已保存。当前总计 {len(updated_all_trajectories)} 条轨迹。") # 2. 清空当前轨迹数据,准备绘制下一个 new_current_ue_data = [] # 返回更新后的数据 return new_current_ue_data, updated_all_trajectories return dash.no_update, dash.no_update # 回调4: 导出所有轨迹到JSON文件 # ... (与前一版本保持一致,功能无变化) @app.callback( Output('export-json-button', 'children'), [Input('export-json-button', 'n_clicks')], [State('all-trajectories-store', 'data')], prevent_initial_call=True ) def export_to_json(n_clicks, all_trajectories_data): """将所有已保存的轨迹数据导出到JSON文件.""" changed_id = [p['prop_id'] for p in dash.callback_context.triggered][0] if 'export-json-button' in changed_id and n_clicks > 0: try: with open(JSON_FILE, 'r') as f: data = json.load(f) data["ue_trajectories"]["trajectory_list"] = all_trajectories_data with open(JSON_FILE, 'w') as f: json.dump(data, f, indent=4) num_trajectories = len(all_trajectories_data) return f"成功导出 ({num_trajectories} 条)!" except Exception as e: return "导出失败!" return '导出所有轨迹到JSON文件' if __name__ == '__main__': # 运行应用 print(f"请安装 shapely (pip install shapely) 并打开浏览器访问 http://127.0.0.1:8050/") app.run(debug=True) 将上面代码中所有中文部分均改为英文,同时添加一个功能,运行前先检查是否存在user_specific_parameter.json文件,若不存在进行创建,user_specific_parameter.json文件格式如下 { "ue_trajectories": { "trajectory_interval_second": 1, "interpolation_interval_second": 0.01, "trajectory_list": []
10-17
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值