实现autoZoom(),画布自适应放缩并居中 @D3.js-v5

实现autoZoom(),画布自适应放缩并居中 @D3.js-v5

需求陈述:

​ 画出了一张节点链接图,虽然可以固定布局中心,但每次使用不同屏幕时,这个布局中心总是会改变,导致节点链接图无法位于画布中央,且大小不适宜,因此需要实现一个自适应放缩方法,使画布按照屏幕的尺寸进行放缩,并将元素居中展示。

image-20230324144012412

解决方案

​ 这是一个画布的嵌套方式。

  • 首先,创建一个<svg>标签(图中为灰绿色),长宽与用户界面/组件的长宽相同。这个<svg>只是一个包裹的容器,一般是不直接在其中放置图元的。
  • 接着,在<svg>内部创建一个<g>标签(图中为黑色),我们真正需要绘制的图元,都会放置在这个<g>标签中。
  • <svg>标签绑定d3.zoom()事件,并将这个zoom事件的transform对象,应用在

​ 只要理解了最后一步,就理解了这整个流程。为什么要把d3.zoom()绑定在外部的<svg>标签上呢?我们需要设想一个场景:假设我们把zoom事件绑定在了内部的<g>标签上,那么当用户将 标签全部拖动到 <svg>外部时,就没办法拖回来了。因为此时用户鼠标已经无法选中 <g>标签了。比如下面这种情况:

image-20230324145631127

​ 因此,为了避免用户将画布拖走后无法拖回来,我们应该设置一个“静止”的窗口,将拖动和放缩事件绑定在这上面,并且将这个事件作用来这个静止窗口内部的元素上。这里的静止窗口就是<svg>,而事件作用的元素就是<g>,这也解释了为什么要选用这种嵌套的形式。

​ 理解了这点,代码就很好写了。我们只需要向放缩的函数中传入外部<svg>的id,内部<g>的id,zoomObj即可。这里还可以传入padding,和duration,设置画布的左右间隙和补间动效。

代码实现

设置用户摁下ctrl键,就自适应放缩并居中
const zoomObj = d3.zoom().scaleExtent([1 / 50, 2]);
document.onkeydown = (e) => {
  if (e.keyCode === 17) {
    autoZoom(
      zoomObj,//传入zoomObj
      'svgContainer',//<svg>的id
      'svg',//<g>的id
      {
        row: 20,
        col: 10
      },//间隙参数(自定)
      1000 // 补间时长(自定)
    )
  s
}
autoZoom()函数实现
//autoZoom() function body
/**
  @param zoomObj: 放缩对象,设置了放缩比率
  @param svgContainerId: 容器 <svg>
  @param svgBodyId: 画布 <g>
  @param marginParam: 间隙参数(自定义)
  @param duration: 补间时长
**/
const autoZoom = (zoomObj, svgContainerId, svgBodyId, marginParam, duration) => {
  const svgContainer = document.querySelector(`#${svgContainerId}`);
  const svgBody = d3.select(`#${svgBodyId}`);
  if (!svgContainer) {
    return;
  }
  const viewBox = svgBody.node().getBBox();//必须用d3.select,才有getBox,获取到长和宽

  //svg(它是静止的)
  const containerWidth = svgContainer.clientWidth//svg标签的宽
  const containerHeight = svgContainer.clientHeight//svg标签的高
  
  // margin setting
  const rowMargin = marginParam.row
  const colMargin = marginParam.col
  
  //计算放缩倍数
  const scale = Math.min((containerWidth - rowMargin) / viewBox.width, (containerHeight - colMargin) / viewBox.height)
  
  //计算如果要居中,画布需要的偏移量
  const offsetX = (containerWidth - rowMargin) / 2 - (viewBox.x + viewBox.width / 2) * scale
  const offsetY = (containerHeight - colMargin) / 2 - (viewBox.y + viewBox.height / 2) * scale

  // d3.zoomIdentity:缩放参数,返回Transform{k:1,x:0,y:0}
  const t = d3.zoomIdentity.translate(offsetX + rowMargin / 2, offsetY).scale(scale)
  
  //计算完毕得到放缩参数t,<svg>标签调用zoomObj和计算好的t
  d3.select(`#${svgContainerId}`).transition().duration(duration).call(zoomObj.transform, t)
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
对于C++基于MFC设置对话框、所有控件和字体自适应放缩程序,可以采用以下步骤: 1. 在对话框类的头文件中添加如下成员变量: ```cpp CRect m_rectDlg; // 对话框矩形区域 CFont m_font; // 字体 ``` 2. 在OnInitDialog函数中初始化成员变量: ```cpp BOOL CMyDialog::OnInitDialog() { CDialogEx::OnInitDialog(); // 获取对话框矩形区域 GetClientRect(m_rectDlg); // 获取字体 LOGFONT lf; GetFont()->GetLogFont(&lf); m_font.CreateFontIndirect(&lf); // 设置所有控件自适应放缩 ModifyStyleEx(0, WS_EX_CONTROLPARENT); return TRUE; } ``` 3. 在OnSize函数中实现对所有控件的自适应放缩: ```cpp void CMyDialog::OnSize(UINT nType, int cx, int cy) { CDialogEx::OnSize(nType, cx, cy); // 获取当前对话框矩形区域 CRect rect; GetClientRect(rect); // 计算放缩比例 float fRateX = (float)rect.Width() / (float)m_rectDlg.Width(); float fRateY = (float)rect.Height() / (float)m_rectDlg.Height(); // 放缩字体 LOGFONT lf; m_font.GetLogFont(&lf); lf.lfHeight = (LONG)(lf.lfHeight * fRateY); m_font.Detach(); m_font.CreateFontIndirect(&lf); SetFont(&m_font); // 放缩控件 CWnd* pChild = GetWindow(GW_CHILD); while (pChild) { CRect rectChild; pChild->GetWindowRect(rectChild); ScreenToClient(rectChild); rectChild.left = (int)(rectChild.left * fRateX); rectChild.right = (int)(rectChild.right * fRateX); rectChild.top = (int)(rectChild.top * fRateY); rectChild.bottom = (int)(rectChild.bottom * fRateY); pChild->MoveWindow(rectChild); pChild = pChild->GetNextWindow(); } // 更新对话框矩形区域 m_rectDlg = rect; } ``` 通过以上步骤,即可实现C++基于MFC设置对话框、所有控件和字体自适应放缩程序。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值