d3.js v5でコンター図を描く
以下のコードでobjcet_functionでコンター図を書きたい対象の関数を定義しています。
x,yはlinspace関数を別途定義してすべての定義域をベクトル化した関数(vect_obj_function)で計算しています。
const margin = {
top: 20,
right: 30,
bottom: 30,
left: 40
}
const width = 700 - margin.left - margin.right
const height = 400 - margin.top - margin.bottom
const linspace = (start, stop, step = 1) => Array(Math.ceil((stop - start) / step)).fill(start).map((v, i) => v + i * step)
const meshgrid = (x, y) => y.map((y_) => x.map((x_) => [x_, y_])).flat()
// 描画したい関数
const object_function = (x, y) => (x ** 2 + y - 11) ** 2 + (x + y ** 2 - 7) ** 2
const vect_obj_function = x => x.map((v) => object_function(v[0], v[1]))
// 描画したい定義域 linspace(min, max, 刻み値)で変数を作ります
// d3.rangeと機能がかぶってしまっていますが独自定義しています
const x = linspace(-6, 6, 0.1)
const y = linspace(-6, 6, 0.1)
const grid = meshgrid(x, y)
const z = vect_obj_function(grid)
// '等高線'の定義. 等間隔じゃなくて2乗則で決めています
const thresholds = [0.001, ...d3.range(0, 10).map(i => Math.pow(2, i))]
const xScale = d3.scaleLinear(d3.extent(x), [0, width]).nice()
const yScale = d3.scaleLinear(d3.extent(y), [height, 0]).nice()
const transform = ({ type, value, coordinates }) => {
return {
type, value,
coordinates: coordinates.map(rings => {
return rings.map(points => {
return points.map(([x, y]) => ([ xScale(x), yScale(y) ]))
})
})
}
}
const contours = d3.contours()
.size([x.length, y.length])
.thresholds(thresholds)(z)
.map(transform)
const color = d3.scaleSequentialLog([d3.max(thresholds), d3.min(thresholds)], d3.interpolateGreys)
const div = d3.select("body")
.append("div")
.attr("class", "contour_tooltip")
.style("font-size", "12px")
.style("position", "absolute")
.style("text-align", "center")
.style("width", "128px")
.style("height", "34px")
.style("background", "#333")
.style("color", "#ddd")
.style("padding", "0px")
.style("border", "0px")
.style("border-radius", "8px")
.style("opacity", "0");
const svg = d3.select("#fig01")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
`translate(${margin.left},${margin.top})`)
svg.append("g")
.attr("fill", "none")
.attr("stroke", "#fff")
.selectAll("path")
.data(contours)
.join("path")
.attr("fill", d => color(d.value))
.attr("stroke", 'white')
.attr("d", d3.geoPath().projection(d3.geoIdentity()
.fitSize([width, height], contours[0])))
.style("stroke-width", 2)
d3.select("#fig01").on("mouseover", () => {
div.transition().duration(400).style("opacity", 0.9);
div.style("z-index", "");
})
.on("mousemove", () => {
const point = d3.mouse(d3.event.target)
const x = xScale.invert(point[0])
const y = yScale.invert(point[1])
const z = object_function(x, y)
div.html(`x = ${x.toFixed(2)} y = ${y.toFixed(2)}<br>f(x,y) = ${z.toFixed(2)}`)
.style("left", (d3.event.pageX + 20) + "px")
.style("top", (d3.event.pageY - 35) + "px");
})
.on("mouseout", () => {
div.transition()
.duration(500)
.style("opacity", 0);
})
svg.append("g")
.attr("transform", `translate(0,${height})`)
.call(d3.axisBottom(xScale))
svg.append("g")
.call(d3.axisLeft(yScale))
No comments:
Post a Comment