为每个分类值分配不同的 y 坐标

Assign distinct y coordinate to each categorical value

我明确要求在 d3 上下文中计算 y 坐标,但我正在努力...

a) 找到 d3(缩放?)组件和使用模式来提供解决方案

b) 确定任何可导航的 d3 API 资源,这将帮助我找到 a)

我有一个事件列表,这些事件具有开始时间和结束时间,并且每个事件都与它们按顺序执行的过程相关联。将其呈现为多轨界面(有点像音频编辑器或甘特图)很自然,其中每个 process/track 都分配有自己的水平泳道,并且事件根据它们的开始水平排列和结束时间。它最终可能看起来有点像这样 view from a research publication

事件数据项引用了它们的进程 ID(在图中显示的进程 ID 是 M1、M2,事件是标有数字的小框)。我知道每个事件的开始和结束时间(确定它们的 x 坐标和宽度),但我需要将操作的矩形放在正确的进程行(y 坐标)中。

事件列表将在开始和结束时不断更新,并可能在未来由 filter/brush 调解。

行数随着进程数的变化而变化。出于这个原因,只有在加入事件列表(并遇到它们引用的所有进程 ID)之后,您才能知道需要多少行、行高以及每个事件的 y 比例位置应该是多少。当一个新的事件项进入指向新进程 ID 的选择时,比例尺应该重新调整以添加一个额外的行。当它退出选择时,它可能是该进程行中的最后一个,因此该行应该消失。

是否有一种模式可以根据事件数据动态列表的(分类)进程 ID 动态分配事件行的 y 坐标?

如何为这种情况找到模式和解决方案?

这是一个使用以下数据模型的简单片段:

[
  {
    label: string; // Row label
    color: string; // Row color
    items: [ { label: string; start: number; end: number; }, ...];
  },
  ...
]

代码段使用 d3.scaleLineard3.axisBottom 例程。

注意:我使用 _top 因为(出于某种神秘原因)top 已经被使用了......我想这是 D3 V5,建议使用V6.

const data = [
  {
    label: 'M1',
    color: 'red',
    items: [
      {label: 'A', start: 10, end: 12},
      {label: 'B', start: 15, end: 20}
    ]
  },
  {
    label: 'M2',
    color: 'orange',
    items: [
      {label: 'C', start: 0, end: 4},
      {label: 'D', start: 4, end: 6},
      {label: 'E', start: 11, end: 17}
    ]
  },
  {
    label: 'M3',
    color: 'yellow',
    items: [
      {label: 'F', start: 0, end: 3},
      {label: 'G', start: 3, end: 9},
      {label: 'H', start: 9, end: 14}
    ]
  }
];


const ticks = 25;
const left = 50;
const _top = 50;
const rowHeight = 20;
const labelWidth = 30;
const bottom = _top + data.length * rowHeight;

const svg = d3.select("svg");

const xScale = d3.scaleLinear()
  .domain([0, ticks])
  .range([0, 300]);
  
const xAxis = d3.axisBottom()
    .scale(xScale);

svg.append("g")
    .attr('transform', `translate(${left}, ${bottom - 1})`)
  .call(xAxis);
      
let i, j;      
for (i = 0; i <= ticks; i++)
    svg.append('line')
    .attr('x1', xScale(i) + left)
    .attr('x2', xScale(i) + left)
    .attr('y1', _top)
    .attr('y2', bottom)
    .style('stroke', 'black');
    
for (i = 0; i < data.length; i++) {
    svg.append('line')
    .attr('x1', left)
    .attr('x2', xScale(ticks) + left)
    .attr('y1', _top + i * rowHeight)
    .attr('y2', _top + i * rowHeight)
    .style('stroke', 'black');
  svg.append('rect')
    .attr('x', left - labelWidth)
    .attr('y', _top + i * rowHeight)
    .attr('width', labelWidth)
    .attr('height', rowHeight)
    .style('fill', data[i].color)
    .style('stroke', 'black')
  svg.append('text')
    .text(data[i].label)
    .attr('x', left - labelWidth / 2)
    .attr('y', _top + (i + 0.5) * rowHeight)
    .attr('text-anchor', 'middle')
    .attr('alignment-baseline', 'middle')
    .style('fill', 'black');
    
  const items = data[i].items;  
  for (j = 0; j < items.length; j++) {
    const x = left + xScale(items[j].start);
    const width = xScale(items[j].end) - xScale(items[j].start);
  svg.append('rect')
    .attr('x', x)
    .attr('y', _top + i * rowHeight)
    .attr('width', width)
    .attr('height', rowHeight)
    .style('fill', data[i].color)
    .style('stroke', 'black')
    
  svg.append('text')
    .text(items[j].label)
    .attr('x', x + width / 2)
    .attr('y', _top + (i + 0.5) * rowHeight)
    .attr('text-anchor', 'middle')
    .attr('alignment-baseline', 'middle')
    .style('fill', 'black');
    
  }  
}    
  
text {
  font-family: 'Ubuntu';
  font-size: 12px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="400" height="200">
</svg>