火柴棍绘制
<ol>
<li>
<p><strong>数据提取与范围设置</strong>:</p>
<pre><code class="language-javascript">const xData = data.frequency.image_data;
const yData = data.lg_energy.image_data;
const xExtent = d3.extent(xData);
const yExtent = d3.extent(yData);</code></pre>
<ul>
<li><code>xData</code> 和 <code>yData</code> 从输入数据中提取出频率和能量的数据数组。</li>
<li><code>d3.extent</code> 用于计算数据的最小值和最大值,分别为 <code>xExtent</code> 和 <code>yExtent</code>。</li>
</ul>
</li>
<li>
<p><strong>检查数据范围</strong>:</p>
<pre><code class="language-javascript">if (xExtent[0] === 0) {
xExtent[0] = xExtent[0];
}
if (xExtent[1] === 0) {
xExtent[1] = xExtent[1];
}
if (yExtent[0] === 0) {
yExtent[0] = yExtent[0];
}
if (yExtent[1] === 0) {
yExtent[1] = yExtent[1];
}</code></pre>
<ul>
<li>这部分代码看起来是多余的,因为它没有改变 <code>xExtent</code> 和 <code>yExtent</code> 的值。如果要进行额外的处理,可以添加实际的逻辑。</li>
</ul>
</li>
<li>
<p><strong>设置图形宽高和容器</strong>:</p>
<pre><code class="language-javascript">const width = imageWidth;
const height = imageHeight;
let gs = g.select('.line-g');
if (gs.size() === 0) {
gs = g.append('g').attr('class', 'line-g').style('pointer-events', 'none');
}
gs.html('');</code></pre>
<ul>
<li><code>width</code> 和 <code>height</code> 被设置为输入的图形尺寸。</li>
<li>选择或创建一个名为 <code>line-g</code> 的 <code>&lt;g&gt;</code> 元素,用于包含所有图形元素,并确保它不响应鼠标事件。</li>
</ul>
</li>
<li>
<p><strong>创建 X 轴和 Y 轴比例尺</strong>:</p>
<pre><code class="language-javascript">const xAxisScale = d3.scaleLinear()
.domain([xExtent[0], xExtent[1]])
.rangeRound([60, width - 60]);
gs.append('g')
.attr('class', 'axis-x')
.attr('transform', `translate(0,${height - 60})`)
.call(d3.axisBottom(xAxisScale).ticks(6))
.selectAll('path,line,text')
.attr('stroke', '#fff');</code></pre>
<ul>
<li><code>xAxisScale</code> 和 <code>yAxisScale</code> 设置了 X 轴和 Y 轴的比例尺,将数据范围映射到图形的像素范围。</li>
<li>将 X 轴添加到图形底部,并设置其刻度和样式。</li>
</ul>
</li>
<li>
<p><strong>绘制 Y 轴</strong>:</p>
<pre><code class="language-javascript">const yAxisScale = d3.scaleLinear()
.domain([yExtent[0], yExtent[1]])
.rangeRound([height - 60, 10]);
gs.append('g')
.attr('class', 'axis-y')
.attr('transform', 'translate(60,10)')
.call(d3.axisLeft(yAxisScale))
.selectAll('path,line,text')
.attr('stroke', '#fff');</code></pre>
<ul>
<li>同样设置 Y 轴比例尺,并将 Y 轴添加到图形左侧。</li>
</ul>
</li>
<li>
<p><strong>添加 X 轴标签</strong>:</p>
<pre><code class="language-javascript">gs.append('text')
.attr('dy', '.75em')
.attr('y', height - 30)
.attr('x', function (d) { return (xAxisScale(xExtent[1]) - xAxisScale(xExtent[0])) / 2 + 60; })
.attr('text-anchor', 'middle')
.attr('font-size', 12)
.text('频率')
.style('fill', '#fff');</code></pre>
<ul>
<li>在 X 轴下方添加“频率”标签,并进行适当的定位和样式设置。</li>
</ul>
</li>
<li>
<p><strong>绘制矩形标记</strong>:</p>
<pre><code class="language-javascript">gs.selectAll('.rect')
.data(yData)
.enter()
.append('rect')
.style('stroke', function (d) { return 'blue'; })
.attr('width', 4)
.attr('height', function (d) {
const y = yAxisScale(d);
return height - 60 - y &lt; 0 ? 0 : height - 60 - y;
})
.style('cursor', 'pointer')
.attr('fill', 'blue')
.style('pointer-events', 'all')
.attr('x', function (d, index) {
const x = xAxisScale(xData[index]) - 2;
return x;
})
.attr('y', function (d, index) {
return yAxisScale(d);
})
.attr('title', (d, index) =&gt; `${d}`)
.attr('r', 5);</code></pre>
<ul>
<li>绘制蓝色矩形标记,位置根据 <code>xData</code> 和 <code>yData</code> 数据设置,并支持鼠标悬停提示。</li>
</ul>
</li>
<li>
<p><strong>绘制圆形标记</strong>:</p>
<pre><code class="language-javascript">gs.selectAll('.circle')
.data(yData)
.enter()
.append('circle')
.style('stroke', function (d) { return 'red'; })
.style('cursor', 'pointer')
.attr('fill', 'red')
.style('pointer-events', 'all')
.attr('cx', function (d, index) {
const x = xAxisScale(xData[index]);
return x;
})
.attr('cy', function (d, index) {
return yAxisScale(d);
})
.attr('title', (d, index) =&gt; `${d}`)
.attr('r', 5);</code></pre>
<ul>
<li>绘制红色圆形标记,位置设置与矩形相同,并支持鼠标悬停提示。</li>
</ul>
</li>
<li><strong>添加能量标签</strong>:
<pre><code class="language-javascript">const txt = gs.append('text')
.attr('dy', '.75em')
.attr('x', 5)
.attr('y', function (d) { return (yAxisScale(yExtent[0]) - yAxisScale(yExtent[1])) / 2 - 30; })
.attr('text-anchor', 'middle')
.attr('font-size', 12)
.style('fill', '#fff');
txt.append('tspan')
.attr('dx', '1em')
.text('能');
txt.append('tspan')
.attr('dy', '1em')
.attr('dx', '-1em')
.text('量');</code></pre>
<ul>
<li>在图形上添加“能量”标签,由两个 <code>tspan</code> 元素组成,用于显示“能”和“量”两个字。</li>
</ul></li>
</ol>
<p>这段代码的核心任务是通过 D3.js 绘制一个带有 X 轴和 Y 轴的散点图,并在图上显示标记和标签。</p>
<p>完整代码</p>
<pre><code class="language-javascript">
scatterPointPOD_DMD(data, imageWidth, imageHeight, g) {
const xData = data.frequency.image_data;
const yData = data.lg_energy.image_data;
const xExtent = d3.extent(xData);
const yExtent = d3.extent(yData);
if (xExtent[0] === 0) {
xExtent[0] = xExtent[0];
}
if (xExtent[1] === 0) {
xExtent[1] = xExtent[1];
}
if (yExtent[0] === 0) {
yExtent[0] = yExtent[0];
}
if (yExtent[1] === 0) {
yExtent[1] = yExtent[1];
}
const width = imageWidth;
const height = imageHeight;
let gs = g.select('.line-g');
if (gs.size() === 0) {
gs = g.append('g').attr('class', 'line-g').style('pointer-events', 'none');
}
gs.html('');
const xAxisScale = d3.scaleLinear()
.domain([xExtent[0], xExtent[1]])
.rangeRound([60, width - 60]);
gs.append('g') // 创建x轴比例尺
.attr('class', 'axis-x')
.attr('transform', `translate(0,${height - 60})`)
.call(d3.axisBottom(xAxisScale).ticks(6))
.selectAll('path,line,text')
.attr('stroke', '#fff');
const yAxisScale = d3.scaleLinear()
.domain([yExtent[0], yExtent[1]])
.rangeRound([height - 60, 10]);
gs.append('g')
.attr('class', 'axis-y')
.attr('transform', 'translate(60,10)')
.call(d3.axisLeft(yAxisScale))
.selectAll('path,line,text')
.attr('stroke', '#fff');
gs.append('text')
.attr('dy', '.75em')
.attr('y', height - 30)
.attr('x', function (d) { return (xAxisScale(xExtent[1]) - xAxisScale(xExtent[0])) / 2 + 60; })
.attr('text-anchor', 'middle')
.attr('font-size', 12)
.text('频率')
.style('fill', '#fff');
gs.selectAll('.rect')
.data(yData)
.enter()
.append('rect')
.style('stroke', function (d) { return 'blue'; })
.attr('width', 4)
.attr('height', function (d) {
const y = yAxisScale(d);
return height - 60 - y &lt; 0 ? 0 : height - 60 - y;
})
.style('cursor', 'pointer')
.attr('fill', 'blue')
.style('pointer-events', 'all')
.attr('x', function (d, index) {
const x = xAxisScale(xData[index]) - 2;
return x;
})
.attr('y', function (d, index) {
return yAxisScale(d);
})
.attr('title', (d, index) =&gt; `${d}`)
.attr('r', 5);
gs.selectAll('.circle')
.data(yData)
.enter()
.append('circle')
.style('stroke', function (d) { return 'red'; })
.style('cursor', 'pointer')
.attr('fill', 'red')
.style('pointer-events', 'all')
.attr('cx', function (d, index) {
const x = xAxisScale(xData[index]);
return x;
})
.attr('cy', function (d, index) {
return yAxisScale(d);
})
.attr('title', (d, index) =&gt; `${d}`)
.attr('r', 5);
const txt = gs.append('text')
.attr('dy', '.75em')
.attr('x', 5)
.attr('y', function (d) { return (yAxisScale(yExtent[0]) - yAxisScale(yExtent[1])) / 2 - 30; })
.attr('text-anchor', 'middle')
.attr('font-size', 12)
.style('fill', '#fff');
txt.append('tspan')
.attr('dx', '1em')
.text('能');
txt.append('tspan')
.attr('dy', '1em')
.attr('dx', '-1em')
.text('量');
}</code></pre>