Excel VBA:给定{X; Y}对Chart.MouseDown事件到{Category; Value}对的转换公式是什么?

我试图find将ChartMouseDown / MouseUp事件的X和Y坐标转换为Axes的坐标系,即一对{Category; Value}的公式。 Chart.MouseDown事件的Excel对象模型参考说:

图表对象客户端坐标中的鼠标指针的X坐标。

我发现这个任务有用的方法和属性:

 Axis: Left, Width MouseDown/MouseUp: x, y ChartObject: Left, Width Application: CentimetersToPoints, InchesToPoints, MeasurementUnit Window: ActivePane, PointsToScreenPixelsX, PointsToScreenPixelsY Pane: PointsToScreenPixelsX, PointsToScreenPixelsY 

我还发现,“鼠标”事件的X和Y值以像素为单位 – 请参阅testing:

 Private Sub m_target_MouseDown(ByVal Button As Long, ByVal Shift As Long, ByVal x As Long, ByVal y As Long) Dim v_l As Long, v_r As Long 'X and Y are in pixels! With m_target v_l = ActiveWindow.ActivePane.PointsToScreenPixelsX(.Parent.Left) v_r = ActiveWindow.ActivePane.PointsToScreenPixelsX(.Parent.Left + .Parent.Width) Debug.Print "X="; x; "Y="; y; "Xc="; x / 12 * 9; "Yc="; y / 12 * 9; VBA.chr$(13); _ "xlCategory: Left="; .Axes(xlCategory).Left; " Top="; .Axes(xlCategory).Top; " Width="; .Axes(xlCategory).Width; VBA.chr$(13); _ "xlValue: Left="; .Axes(xlValue).Left; " Top="; .Axes(xlValue).Top; " Width="; .Axes(xlValue).Width; VBA.chr$(13); _ "PlotArea: Left="; .PlotArea.Left; " Top="; .PlotArea.Top; " Width="; .PlotArea.Width; " Height="; .PlotArea.Height; VBA.chr$(13); _ "PlotArea: InsideLeft="; .PlotArea.InsideLeft; " InsideTop="; .PlotArea.InsideTop; " InsideWidth="; .PlotArea.InsideWidth; " InsideHeight="; .PlotArea.InsideHeight; VBA.chr$(13); _ "ChartObject: Left="; .Parent.Left; " Top="; .Parent.Top; ", Width="; .Parent.Width; " Height="; .Parent.Height; VBA.chr$(13); _ "ActiveWindow: Caption="; ActiveWindow.Caption; " Left="; ActiveWindow.Left; " UsableWidth="; ActiveWindow.UsableWidth; " Width="; ActiveWindow.Width; VBA.chr$(13); _ "ChartObject: Left_px="; ActiveWindow.ActivePane.PointsToScreenPixelsX(.Parent.Left); " Width_px="; v_r - v_l End With End Sub 

比例9/12被发现在“Excel VBA:为什么PointToScreenPixelsX为窗口和窗格返回值之间的差距如此之大?

单击图表的右侧边框会显示以下输出:

 X= 1838 Y= 220 Xc= 1378.5 Yc= 165 xlCategory: Left= 56 Top= 186 Width= 1286 xlValue: Left= 35 Top= 2 Width= 21 PlotArea: Left= 34.7092125984252 Top=-4 Width= 1320.84480314961 Height= 210 PlotArea: InsideLeft= 55.6250393700787 InsideTop= 2.10251968503937 InsideWidth= 1286.32645669291 InsideHeight= 183.845826771654 ChartObject: Left= 132.374954223633 Top= 0 , Width= 1377.72351074219 Height= 210 ActiveWindow: Caption=Q99708-VSSPVFGATSSIK-DC-1147-DC-r0001-dn.xlsx Left=-2 UsableWidth= 1279.5 Width= 1298.25 ChartObject: Left_px=-310 Width_px= 1837 

Width_px接近于X并通过使用v_r - v_l将左(v_l)和右(v_r)边界转换为像素来计算为v_r - v_l

但如何find确切的公式来获得{X; Y} {分类;价值}应该适用于任何决议(像素每英寸),MeasurementUnit,Pane.Left /宽度,Chart.Left /宽度,PlotArea.Left /宽度和Axis.Left / Width?

我find了这个公式。 以下惊喜相当复杂:

  1. ChartArea.Width <> ChartObject.WidthChartArea.Height <> ChartObject.Height
  2. 可以将graphics坐标系中的形状移出正坐标。

我在图表中创build了线条形状,并将线条移动到图表的左侧约束。 线的左边是-4! 然后我试图将Shape.Left属性设置为-4。 这不起作用:赋值后它的值立即变为0。 但Shape.IncrementLeft方法成功地将行移动到图表的左侧约束(-4)。 类似地, Shape.IncrementTop方法更改一个Shape.Top属性值。 结果发现了从像素到点的转换。

然后通过实验发现,必须使用PloatArea.Inside<Left,Top,Width,Height>来代替Axis的相应属性,才能将{X; Y}对的点转换为{Category; Value}对。

首先,使用线形移动到左侧和顶部约束,查找ChartObject对象在ChartObject中的移位:

 Sub extract_transform(ByVal p_chart As Chart, ByRef p_sh_x As Double, ByRef p_sh_y As Double) Dim v_ScreenUpdating As Boolean, v_sh As Double With p_chart.Shapes.AddShape(msoLine, 0, 0, 20, 0) p_sh_x = 0: .IncrementLeft -10 While p_sh_x <> .Left p_sh_x = .Left .IncrementLeft -10 'Move to the left constaint Wend p_sh_y = 0: .IncrementTop -10 While p_sh_y <> .Top p_sh_y = .Top .IncrementTop -10 'Move to the top constaint Wend .delete 'Collecting garbage End With End Sub 

在Chart坐标系统中使用ChartArea对象的{left; top}查找点的转换如下:

 Sub pixels2points(ByVal p_pane As Pane, ByVal x_pi As Long, ByVal y_pi As Long, ByRef x_pt As Double, ByRef y_pt As Double) Dim v_sc As Double With p_pane v_sc = (.PointsToScreenPixelsX(1000) - .PointsToScreenPixelsX(0)) / 1000 x_pt = m_sh_x + x_pi / v_sc v_sc = (.PointsToScreenPixelsY(1000) - .PointsToScreenPixelsY(0)) / 1000 y_pt = m_sh_y + y_pi / v_sc End With End Sub 

pixels2points假定x_piy_pi在图表坐标系中以像素为单位。 m_sh_xm_sh_yDoubletypes的,并且是类c_report的成员。 p_pane是一个关键对象:只有Pane.PointsToScreenPixels<X,Y>方法将点转换为像素,并且取决于窗口的缩放因子。 同样的工作表可能出现在很多窗口中,因此必须使用相应的Pane对象。

要将{X; Y}对以点转换为{Category; Value}对,图表包装类具有以下方法:

 Sub pt2axes(ByVal p_axes As Axes, ByVal p_pa As PlotArea, ByVal x_pt As Double, ByVal y_pt As Double, ByRef p_cat As Double, ByRef p_val As Double) With p_axes(xlCategory) p_cat = (x_pt - p_pa.InsideLeft) / p_pa.InsideWidth * (.MaximumScale - .MinimumScale) + .MinimumScale End With With p_axes(xlValue) p_val = ((p_pa.InsideTop + p_pa.InsideHeight) - y_pt) / p_pa.InsideHeight * (.MaximumScale - .MinimumScale) + .MinimumScale End With End Sub 

Y的转换公式假定坐标的原点位于图表的底部。

现在这些方法在处理Chart.MouseDown事件中的应用:

 Private Sub m_target_MouseDown(ByVal Button As Long, ByVal Shift As Long, ByVal x As Long, ByVal y As Long) Dim v_x As Double, v_y As Double, v_cat As Double, v_val as Double With m_target Debug.Print "X="; x; "Y="; y m_report.pixels2points ActiveWindow.ActivePane, x, y, v_x, v_y pt2axes .Axes, .PlotArea, v_x, v_y, v_cat, v_val Debug.Print "cat="; v_cat; "val="; v_val End With End Sub 

m_target类成员的types为Chartm_report类成员的types为c_report ,并作为具有pixels2points方法的父对象的引用。

为了testing公式,我们使用了宽范围的图表,400%的冷冻窗格和卷轴,点击了具有可见坐标{270; 2}和{10; 6}的交叉网格线上的点。 结果立即窗口的输出是:

 X= 6980 Y= 849 cat= 270.030767164932 val= 2.00522623783961 X= 564 Y= 522 cat= 9.97421024128619 val= 6.00822155418118 

假设类别轴出现差异+/-0.03 ,因为像素是离散的,垂直网格线步长没有以像素为单位的整数值。