From 1c8b310b27b736ef9ad623533a104c15796151e9 Mon Sep 17 00:00:00 2001 From: "min.tian" Date: Mon, 6 Jun 2022 08:49:03 +0800 Subject: [PATCH 1/4] optimize the voronoi layout of ivf_flat when search Signed-off-by: min.tian --- federjs/FederView/IvfflatView/index.js | 3 +- .../layout/SVCoarseVoronoiHandler.js | 285 +++++++++--------- .../IvfflatView/layout/overviewLayout.js | 125 ++++---- .../IvfflatView/layout/searchViewLayout.js | 1 - test/bundle.js | 154 ++++++---- test/test.js | 2 +- 6 files changed, 300 insertions(+), 270 deletions(-) diff --git a/federjs/FederView/IvfflatView/index.js b/federjs/FederView/IvfflatView/index.js index b99d5fc..ee6e764 100644 --- a/federjs/FederView/IvfflatView/index.js +++ b/federjs/FederView/IvfflatView/index.js @@ -87,6 +87,7 @@ export default class IvfflatView extends BaseView { renderVoronoiView.call(this); } async searchViewHandler({ searchRes }) { + this.overviewInitPromise && (await this.overviewInitPromise); this.nprobe = searchRes.csResIds.length; this.k = searchRes.fsResIds.length; this.colorScheme = d3 @@ -98,7 +99,7 @@ export default class IvfflatView extends BaseView { }) .then(() => {}); await this.searchViewInitPromise; - console.log('searchViewHandler finished'); + console.log('searchView Layout finished'); } renderSearchView() { this.renderCoarseSearch(); diff --git a/federjs/FederView/IvfflatView/layout/SVCoarseVoronoiHandler.js b/federjs/FederView/IvfflatView/layout/SVCoarseVoronoiHandler.js index 1fa3046..216ed10 100644 --- a/federjs/FederView/IvfflatView/layout/SVCoarseVoronoiHandler.js +++ b/federjs/FederView/IvfflatView/layout/SVCoarseVoronoiHandler.js @@ -3,158 +3,153 @@ import getVoronoi from './getVoronoi'; import { vecSort } from 'Utils'; export default function SVCoarseVoronoiHandler() { - const width = this.width; - const height = this.height; - const clusters = this.clusters; - const targetClusterId = this.searchRes.coarse[0].id; - const targetCluster = clusters.find( - (cluster) => cluster.clusterId === targetClusterId - ); - const otherFineClustersId = this.searchRes.csResIds.filter( - (clusterId) => clusterId !== targetClusterId - ); - // console.log('fineNode', fineNode); - // console.log('otherFineClustersId', otherFineClustersId); - const links = otherFineClustersId.map((clusterId) => ({ - source: clusterId, - target: targetClusterId, - })); - clusters.forEach((cluster) => { - cluster.x = cluster.forceProjection[0]; - cluster.y = cluster.forceProjection[1]; - }); - const otherFineCluster = clusters.filter( - (cluster) => otherFineClustersId.indexOf(cluster.clusterId) >= 0 - ); - otherFineCluster.forEach((cluster, i) => { - cluster.x = - targetCluster.x + - (targetCluster.r / 2) * - Math.sin(((2 * Math.PI) / otherFineCluster.length) * i); - cluster.y = - targetCluster.y - - (targetCluster.r / 2) * - Math.cos(((2 * Math.PI) / otherFineCluster.length) * i); - }); + return new Promise((resolve) => { + const width = this.width; + const height = this.height; + const clusters = this.clusters; - const simulation = d3 - .forceSimulation(clusters) - .force( - 'links', - d3 - .forceLink(links) - .id((cluster) => cluster.clusterId) - .strength((_) => 0.1) - ) - .force( - 'collision', - d3 - .forceCollide() - .radius((cluster) => cluster.r) - .strength(0.1) - ) - .force('center', d3.forceCenter(width / 2, height / 2)) - .on('tick', () => { - // border - clusters.forEach((cluster) => { - cluster.x = Math.max(cluster.r, Math.min(width - cluster.r, cluster.x)); - cluster.y = Math.max( - cluster.r, - Math.min(height - cluster.r, cluster.y) - ); - }); + const fineClusterOrder = vecSort( + this.nprobeClusters, + 'OVPolyCentroid', + 'clusterId' + ); + // console.log('fineClusterOrder', fineClusterOrder); + + const targetClusterId = this.searchRes.coarse[0].id; + const targetCluster = clusters.find( + (cluster) => cluster.clusterId === targetClusterId + ); + const otherFineClustersId = fineClusterOrder.filter( + (clusterId) => clusterId !== targetClusterId + ); + const links = otherFineClustersId.map((clusterId) => ({ + source: clusterId, + target: targetClusterId, + })); + clusters.forEach((cluster) => { + cluster.x = cluster.forceProjection[0]; + cluster.y = cluster.forceProjection[1]; }); + const targetClusterX = + this.nprobeClusters.reduce((acc, cluster) => acc + cluster.x, 0) / + this.nprobe; + const targetClusterY = + this.nprobeClusters.reduce((acc, cluster) => acc + cluster.y, 0) / + this.nprobe; + targetCluster.x = targetClusterX; + targetCluster.y = targetClusterY; - return new Promise((resolve) => { - setTimeout(() => { - simulation.stop(); - clusters.forEach((cluster) => { - cluster.SVPos = [cluster.x, cluster.y]; - }); - const voronoi = getVoronoi(clusters, width, height); - clusters.forEach((cluster, i) => { - const points = voronoi.cellPolygon(i); - points.pop(); - cluster.SVPolyPoints = points; - cluster.SVPolyCentroid = d3.polygonCentroid(points); - }); - this.SVVoronoi = voronoi; + const otherFineCluster = otherFineClustersId.map((clusterId) => + this.nprobeClusters.find((cluster) => cluster.clusterId === clusterId) + ); + const angleStep = (2 * Math.PI) / (this.nprobe - 1); + const biasR = targetCluster.r * 0.5; + otherFineCluster.forEach((cluster, i) => { + cluster.x = targetClusterX + biasR * Math.sin(angleStep * i); + cluster.y = targetClusterY + biasR * Math.cos(angleStep * i); + }); - const targetCluster = clusters.find( - (cluster) => cluster.clusterId === targetClusterId - ); - const centoid_fineClusters_x = - this.nprobeClusters.reduce( - (acc, cluster) => acc + cluster.SVPolyCentroid[0], - 0 - ) / this.nprobeClusters.length; - const centroid_fineClusters_y = - this.nprobeClusters.reduce( - (acc, cluster) => acc + cluster.SVPolyCentroid[1], - 0 - ) / this.nprobeClusters.length; - const _x = centoid_fineClusters_x - targetCluster.SVPos[0]; - const _y = centroid_fineClusters_y - targetCluster.SVPos[1]; - const biasR = Math.sqrt(_x * _x + _y * _y); - const targetNode = { - SVPos: [ - targetCluster.SVPos[0] + targetCluster.r * 0.4 * (_x / biasR), - targetCluster.SVPos[1] + targetCluster.r * 0.4 * (_y / biasR), - ], - }; - // let randAngle = Math.random() * Math.PI * 2; - // let randBias = [Math.sin, Math.cos].map( - // (f) => targetCluster.r * 0.6 * f(randAngle) - // ); - // const targetNode = { - // SVPos: targetCluster.SVPos.map( - // (d, i) => d + randBias[i] - // ), - // }; - targetNode.isLeft_coarseLevel = targetNode.SVPos[0] < this.width / 2; - this.targetNode = targetNode; - // console.log('targetNode', targetNode); + const simulation = d3 + .forceSimulation(clusters) + .alphaDecay(1 - Math.pow(0.001, 1 / 50)) + .force( + 'links', + d3 + .forceLink(links) + .id((cluster) => cluster.clusterId) + .strength((_) => 0.15) + ) + .force( + 'collision', + d3 + .forceCollide() + .radius((cluster) => cluster.r) + .strength(0.1) + ) + .force('center', d3.forceCenter(width / 2, height / 2)) + .on('tick', () => { + // border + clusters.forEach((cluster) => { + cluster.x = Math.max( + cluster.r, + Math.min(width - cluster.r, cluster.x) + ); + cluster.y = Math.max( + cluster.r, + Math.min(height - cluster.r, cluster.y) + ); + }); + }) + .on('end', () => { + clusters.forEach((cluster) => { + cluster.SVPos = [cluster.x, cluster.y]; + }); + const voronoi = getVoronoi(clusters, width, height); + clusters.forEach((cluster, i) => { + const points = voronoi.cellPolygon(i); + points.pop(); + cluster.SVPolyPoints = points; + cluster.SVPolyCentroid = d3.polygonCentroid(points); + }); + this.SVVoronoi = voronoi; - const polarOrigin = [ - width / 2 + - (targetNode.isLeft_coarseLevel ? -1 : 1) * - this.polarOriginBias * - width, - height / 2, - ]; - // const polarOrigin = [width / 2, height / 2]; - this.polarOrigin = polarOrigin; - targetNode.polarPos = polarOrigin; - const polarMaxR = Math.min(width, height) * 0.5 - 5; - this.polarMaxR = polarMaxR; + const targetCluster = clusters.find( + (cluster) => cluster.clusterId === targetClusterId + ); + const centoid_fineClusters_x = + this.nprobeClusters.reduce( + (acc, cluster) => acc + cluster.SVPolyCentroid[0], + 0 + ) / this.nprobeClusters.length; + const centroid_fineClusters_y = + this.nprobeClusters.reduce( + (acc, cluster) => acc + cluster.SVPolyCentroid[1], + 0 + ) / this.nprobeClusters.length; + const _x = centoid_fineClusters_x - targetCluster.SVPos[0]; + const _y = centroid_fineClusters_y - targetCluster.SVPos[1]; + const biasR = Math.sqrt(_x * _x + _y * _y); + const targetNode = { + SVPos: [ + targetCluster.SVPos[0] + targetCluster.r * 0.4 * (_x / biasR), + targetCluster.SVPos[1] + targetCluster.r * 0.4 * (_y / biasR), + ], + }; + targetNode.isLeft_coarseLevel = targetNode.SVPos[0] < this.width / 2; + this.targetNode = targetNode; - // const fineClusterOrder = vecSort( - // this.nprobeClusters, - // 'SVPolyCentroid', - // 'clusterId' - // ); - // console.log('fineClusterOrder', fineClusterOrder, ) - const fineClusterOrder = this.searchRes.csResIds; - const angleStep = (Math.PI * 2) / fineClusterOrder.length; - this.nprobeClusters.forEach((cluster) => { - const order = fineClusterOrder.indexOf(cluster.clusterId); - cluster.polarOrder = order; - cluster.SVNextLevelPos = [ - polarOrigin[0] + (polarMaxR / 2) * Math.sin(angleStep * order), - polarOrigin[1] + (polarMaxR / 2) * Math.cos(angleStep * order), - ]; - cluster.SVNextLevelTran = [ - cluster.SVNextLevelPos[0] - cluster.SVPolyCentroid[0], - cluster.SVNextLevelPos[1] - cluster.SVPolyCentroid[1], + const polarOrigin = [ + width / 2 + + (targetNode.isLeft_coarseLevel ? -1 : 1) * + this.polarOriginBias * + width, + height / 2, ]; - }); - const clusterId2cluster = {}; - this.nprobeClusters.forEach((cluster) => { - clusterId2cluster[cluster.clusterId] = cluster; - }); - this.clusterId2cluster = clusterId2cluster; + // const polarOrigin = [width / 2, height / 2]; + this.polarOrigin = polarOrigin; + targetNode.polarPos = polarOrigin; + const polarMaxR = Math.min(width, height) * 0.5 - 5; + this.polarMaxR = polarMaxR; + const angleStep = (Math.PI * 2) / fineClusterOrder.length; + this.nprobeClusters.forEach((cluster) => { + const order = fineClusterOrder.indexOf(cluster.clusterId); + cluster.polarOrder = order; + cluster.SVNextLevelPos = [ + polarOrigin[0] + (polarMaxR / 2) * Math.sin(angleStep * order), + polarOrigin[1] + (polarMaxR / 2) * Math.cos(angleStep * order), + ]; + cluster.SVNextLevelTran = [ + cluster.SVNextLevelPos[0] - cluster.SVPolyCentroid[0], + cluster.SVNextLevelPos[1] - cluster.SVPolyCentroid[1], + ]; + }); + const clusterId2cluster = {}; + this.nprobeClusters.forEach((cluster) => { + clusterId2cluster[cluster.clusterId] = cluster; + }); + this.clusterId2cluster = clusterId2cluster; - resolve(); - }, this.voronoiForceTime / 2); + resolve(); + }); }); } diff --git a/federjs/FederView/IvfflatView/layout/overviewLayout.js b/federjs/FederView/IvfflatView/layout/overviewLayout.js index 231b93f..d9093ce 100644 --- a/federjs/FederView/IvfflatView/layout/overviewLayout.js +++ b/federjs/FederView/IvfflatView/layout/overviewLayout.js @@ -2,70 +2,75 @@ import * as d3 from 'd3'; import getVoronoi from './getVoronoi'; export default function overviewLayoutHandler({ indexMeta }) { - const width = this.width; - const height = this.height; - const allArea = width * height; - const { ntotal, listCentroidProjections = null, listSizes } = indexMeta; - const clusters = listSizes.map((listSize, i) => ({ - clusterId: i, - oriProjection: listCentroidProjections - ? listCentroidProjections[i] - : [Math.random(), Math.random()], - count: listSize, - countP: listSize / ntotal, - countArea: allArea * (listSize / ntotal), - })); - - const x = d3 - .scaleLinear() - .domain(d3.extent(clusters, (cluster) => cluster.oriProjection[0])) - .range([0, width]); - const y = d3 - .scaleLinear() - .domain(d3.extent(clusters, (cluster) => cluster.oriProjection[1])) - .range([0, height]); + return new Promise((resolve) => { + const width = this.width; + const height = this.height; + const allArea = width * height; + const { ntotal, listCentroidProjections = null, listSizes } = indexMeta; + const clusters = listSizes.map((listSize, i) => ({ + clusterId: i, + oriProjection: listCentroidProjections + ? listCentroidProjections[i] + : [Math.random(), Math.random()], + count: listSize, + countP: listSize / ntotal, + countArea: allArea * (listSize / ntotal), + })); - clusters.forEach((cluster) => { - cluster.x = x(cluster.oriProjection[0]); - cluster.y = y(cluster.oriProjection[1]); - cluster.r = Math.max( - this.minVoronoiRadius * this.canvasScale, - Math.sqrt(cluster.countArea / Math.PI) - ); - }); + const x = d3 + .scaleLinear() + .domain(d3.extent(clusters, (cluster) => cluster.oriProjection[0])) + .range([0, width]); + const y = d3 + .scaleLinear() + .domain(d3.extent(clusters, (cluster) => cluster.oriProjection[1])) + .range([0, height]); - const simulation = d3 - .forceSimulation(clusters) - .force( - 'collision', - d3.forceCollide().radius((cluster) => cluster.r) - ) - .force('center', d3.forceCenter(width / 2, height / 2)) - .on('tick', () => { - // border - clusters.forEach((cluster) => { - cluster.x = Math.max(cluster.r, Math.min(width - cluster.r, cluster.x)); - cluster.y = Math.max( - cluster.r, - Math.min(height - cluster.r, cluster.y) - ); - }); + clusters.forEach((cluster) => { + cluster.x = x(cluster.oriProjection[0]); + cluster.y = y(cluster.oriProjection[1]); + cluster.r = Math.max( + this.minVoronoiRadius * this.canvasScale, + Math.sqrt(cluster.countArea / Math.PI) + ); }); - return new Promise((resolve) => { - setTimeout(() => { - simulation.stop(); - clusters.forEach((cluster) => { - cluster.forceProjection = [cluster.x, cluster.y]; - }); - const voronoi = getVoronoi(clusters, width, height); - clusters.forEach((cluster, i) => { - const points = voronoi.cellPolygon(i); - points.pop(); - cluster.OVPolyPoints = points; - cluster.OVPolyCentroid = d3.polygonCentroid(points); + const simulation = d3 + .forceSimulation(clusters) + .alphaDecay(1 - Math.pow(0.001, 1 / 100)) + .force( + 'collision', + d3.forceCollide().radius((cluster) => cluster.r) + ) + .force('center', d3.forceCenter(width / 2, height / 2)) + .on('tick', () => { + // border + clusters.forEach((cluster) => { + cluster.x = Math.max( + cluster.r, + Math.min(width - cluster.r, cluster.x) + ); + cluster.y = Math.max( + cluster.r, + Math.min(height - cluster.r, cluster.y) + ); + }); + + // test += 1; + // console.log('test', test); + }) + .on('end', () => { + clusters.forEach((cluster) => { + cluster.forceProjection = [cluster.x, cluster.y]; + }); + const voronoi = getVoronoi(clusters, width, height); + clusters.forEach((cluster, i) => { + const points = voronoi.cellPolygon(i); + points.pop(); + cluster.OVPolyPoints = points; + cluster.OVPolyCentroid = d3.polygonCentroid(points); + }); + resolve({ clusters, voronoi }); }); - resolve({ clusters, voronoi }); - }, this.voronoiForceTime); }); } diff --git a/federjs/FederView/IvfflatView/layout/searchViewLayout.js b/federjs/FederView/IvfflatView/layout/searchViewLayout.js index c80ba52..3e20e41 100644 --- a/federjs/FederView/IvfflatView/layout/searchViewLayout.js +++ b/federjs/FederView/IvfflatView/layout/searchViewLayout.js @@ -4,7 +4,6 @@ import SVFineProjectHandler from './SVFineProjectHandler'; export default function searchViewLayoutHandler({ searchRes }) { const SVCoarsePromise = new Promise(async (resolve) => { - this.overviewInitPromise && (await this.overviewInitPromise); this.searchRes = searchRes; searchRes.coarse.forEach(({ id, dis }) => (this.clusters[id].dis = dis)); this.nprobeClusters = this.clusters.filter( diff --git a/test/bundle.js b/test/bundle.js index 9a63f07..310907c 100644 --- a/test/bundle.js +++ b/test/bundle.js @@ -13455,6 +13455,34 @@ ${indentData}`); var polyPoints2path = (points, withZ = true) => { return `M${points.join("L")}${withZ ? "Z" : ""}`; }; + var calAngle = (x3, y4) => { + let angle = Math.atan(x3 / y4) / Math.PI * 180; + if (angle < 0) { + if (x3 < 0) { + angle += 360; + } else { + angle += 180; + } + } else { + if (x3 < 0) { + angle += 180; + } + } + return angle; + }; + var vecSort = (vecs, layoutKey, returnKey) => { + const center = { + x: vecs.reduce((acc, c2) => acc + c2[layoutKey][0], 0) / vecs.length, + y: vecs.reduce((acc, c2) => acc + c2[layoutKey][1], 0) / vecs.length + }; + const angles = vecs.map((vec2) => ({ + _vecSortAngle: calAngle(vec2[layoutKey][0] - center.x, vec2[layoutKey][1] - center.y), + _key: vec2[returnKey] + })); + angles.sort((a2, b) => a2._vecSortAngle - b._vecSortAngle); + const res = angles.map((vec2) => vec2._key); + return res; + }; var dist2 = (vec1, vec2) => vec1.map((num, i) => num - vec2[i]).reduce((acc, cur) => acc + cur * cur, 0); var dist3 = (vec1, vec2) => Math.sqrt(dist2(vec1, vec2)); var deDupLink = (links, source = "source", target = "target") => { @@ -16373,33 +16401,31 @@ ${indentData}`); // federjs/FederView/IvfflatView/layout/overviewLayout.js function overviewLayoutHandler2({ indexMeta }) { - const width = this.width; - const height = this.height; - const allArea = width * height; - const { ntotal, listCentroidProjections = null, listSizes } = indexMeta; - const clusters = listSizes.map((listSize, i) => ({ - clusterId: i, - oriProjection: listCentroidProjections ? listCentroidProjections[i] : [Math.random(), Math.random()], - count: listSize, - countP: listSize / ntotal, - countArea: allArea * (listSize / ntotal) - })); - const x3 = linear2().domain(extent(clusters, (cluster) => cluster.oriProjection[0])).range([0, width]); - const y4 = linear2().domain(extent(clusters, (cluster) => cluster.oriProjection[1])).range([0, height]); - clusters.forEach((cluster) => { - cluster.x = x3(cluster.oriProjection[0]); - cluster.y = y4(cluster.oriProjection[1]); - cluster.r = Math.max(this.minVoronoiRadius * this.canvasScale, Math.sqrt(cluster.countArea / Math.PI)); - }); - const simulation = simulation_default(clusters).force("collision", collide_default().radius((cluster) => cluster.r)).force("center", center_default(width / 2, height / 2)).on("tick", () => { + return new Promise((resolve) => { + const width = this.width; + const height = this.height; + const allArea = width * height; + const { ntotal, listCentroidProjections = null, listSizes } = indexMeta; + const clusters = listSizes.map((listSize, i) => ({ + clusterId: i, + oriProjection: listCentroidProjections ? listCentroidProjections[i] : [Math.random(), Math.random()], + count: listSize, + countP: listSize / ntotal, + countArea: allArea * (listSize / ntotal) + })); + const x3 = linear2().domain(extent(clusters, (cluster) => cluster.oriProjection[0])).range([0, width]); + const y4 = linear2().domain(extent(clusters, (cluster) => cluster.oriProjection[1])).range([0, height]); clusters.forEach((cluster) => { - cluster.x = Math.max(cluster.r, Math.min(width - cluster.r, cluster.x)); - cluster.y = Math.max(cluster.r, Math.min(height - cluster.r, cluster.y)); + cluster.x = x3(cluster.oriProjection[0]); + cluster.y = y4(cluster.oriProjection[1]); + cluster.r = Math.max(this.minVoronoiRadius * this.canvasScale, Math.sqrt(cluster.countArea / Math.PI)); }); - }); - return new Promise((resolve) => { - setTimeout(() => { - simulation.stop(); + const simulation = simulation_default(clusters).alphaDecay(1 - Math.pow(1e-3, 1 / 100)).force("collision", collide_default().radius((cluster) => cluster.r)).force("center", center_default(width / 2, height / 2)).on("tick", () => { + clusters.forEach((cluster) => { + cluster.x = Math.max(cluster.r, Math.min(width - cluster.r, cluster.x)); + cluster.y = Math.max(cluster.r, Math.min(height - cluster.r, cluster.y)); + }); + }).on("end", () => { clusters.forEach((cluster) => { cluster.forceProjection = [cluster.x, cluster.y]; }); @@ -16411,7 +16437,7 @@ ${indentData}`); cluster.OVPolyCentroid = centroid_default(points); }); resolve({ clusters, voronoi }); - }, this.voronoiForceTime); + }); }); } @@ -16660,34 +16686,39 @@ ${indentData}`); // federjs/FederView/IvfflatView/layout/SVCoarseVoronoiHandler.js function SVCoarseVoronoiHandler() { - const width = this.width; - const height = this.height; - const clusters = this.clusters; - const targetClusterId = this.searchRes.coarse[0].id; - const targetCluster = clusters.find((cluster) => cluster.clusterId === targetClusterId); - const otherFineClustersId = this.searchRes.csResIds.filter((clusterId) => clusterId !== targetClusterId); - const links = otherFineClustersId.map((clusterId) => ({ - source: clusterId, - target: targetClusterId - })); - clusters.forEach((cluster) => { - cluster.x = cluster.forceProjection[0]; - cluster.y = cluster.forceProjection[1]; - }); - const otherFineCluster = clusters.filter((cluster) => otherFineClustersId.indexOf(cluster.clusterId) >= 0); - otherFineCluster.forEach((cluster, i) => { - cluster.x = targetCluster.x + targetCluster.r / 2 * Math.sin(2 * Math.PI / otherFineCluster.length * i); - cluster.y = targetCluster.y - targetCluster.r / 2 * Math.cos(2 * Math.PI / otherFineCluster.length * i); - }); - const simulation = simulation_default(clusters).force("links", link_default(links).id((cluster) => cluster.clusterId).strength((_) => 0.1)).force("collision", collide_default().radius((cluster) => cluster.r).strength(0.1)).force("center", center_default(width / 2, height / 2)).on("tick", () => { + return new Promise((resolve) => { + const width = this.width; + const height = this.height; + const clusters = this.clusters; + const fineClusterOrder = vecSort(this.nprobeClusters, "OVPolyCentroid", "clusterId"); + const targetClusterId = this.searchRes.coarse[0].id; + const targetCluster = clusters.find((cluster) => cluster.clusterId === targetClusterId); + const otherFineClustersId = fineClusterOrder.filter((clusterId) => clusterId !== targetClusterId); + const links = otherFineClustersId.map((clusterId) => ({ + source: clusterId, + target: targetClusterId + })); clusters.forEach((cluster) => { - cluster.x = Math.max(cluster.r, Math.min(width - cluster.r, cluster.x)); - cluster.y = Math.max(cluster.r, Math.min(height - cluster.r, cluster.y)); + cluster.x = cluster.forceProjection[0]; + cluster.y = cluster.forceProjection[1]; }); - }); - return new Promise((resolve) => { - setTimeout(() => { - simulation.stop(); + const targetClusterX = this.nprobeClusters.reduce((acc, cluster) => acc + cluster.x, 0) / this.nprobe; + const targetClusterY = this.nprobeClusters.reduce((acc, cluster) => acc + cluster.y, 0) / this.nprobe; + targetCluster.x = targetClusterX; + targetCluster.y = targetClusterY; + const otherFineCluster = otherFineClustersId.map((clusterId) => this.nprobeClusters.find((cluster) => cluster.clusterId === clusterId)); + const angleStep = 2 * Math.PI / (this.nprobe - 1); + const biasR = targetCluster.r * 0.5; + otherFineCluster.forEach((cluster, i) => { + cluster.x = targetClusterX + biasR * Math.sin(angleStep * i); + cluster.y = targetClusterY + biasR * Math.cos(angleStep * i); + }); + const simulation = simulation_default(clusters).alphaDecay(1 - Math.pow(1e-3, 1 / 50)).force("links", link_default(links).id((cluster) => cluster.clusterId).strength((_) => 0.15)).force("collision", collide_default().radius((cluster) => cluster.r).strength(0.1)).force("center", center_default(width / 2, height / 2)).on("tick", () => { + clusters.forEach((cluster) => { + cluster.x = Math.max(cluster.r, Math.min(width - cluster.r, cluster.x)); + cluster.y = Math.max(cluster.r, Math.min(height - cluster.r, cluster.y)); + }); + }).on("end", () => { clusters.forEach((cluster) => { cluster.SVPos = [cluster.x, cluster.y]; }); @@ -16704,11 +16735,11 @@ ${indentData}`); const centroid_fineClusters_y = this.nprobeClusters.reduce((acc, cluster) => acc + cluster.SVPolyCentroid[1], 0) / this.nprobeClusters.length; const _x = centoid_fineClusters_x - targetCluster2.SVPos[0]; const _y = centroid_fineClusters_y - targetCluster2.SVPos[1]; - const biasR = Math.sqrt(_x * _x + _y * _y); + const biasR2 = Math.sqrt(_x * _x + _y * _y); const targetNode = { SVPos: [ - targetCluster2.SVPos[0] + targetCluster2.r * 0.4 * (_x / biasR), - targetCluster2.SVPos[1] + targetCluster2.r * 0.4 * (_y / biasR) + targetCluster2.SVPos[0] + targetCluster2.r * 0.4 * (_x / biasR2), + targetCluster2.SVPos[1] + targetCluster2.r * 0.4 * (_y / biasR2) ] }; targetNode.isLeft_coarseLevel = targetNode.SVPos[0] < this.width / 2; @@ -16721,14 +16752,13 @@ ${indentData}`); targetNode.polarPos = polarOrigin; const polarMaxR = Math.min(width, height) * 0.5 - 5; this.polarMaxR = polarMaxR; - const fineClusterOrder = this.searchRes.csResIds; - const angleStep = Math.PI * 2 / fineClusterOrder.length; + const angleStep2 = Math.PI * 2 / fineClusterOrder.length; this.nprobeClusters.forEach((cluster) => { const order = fineClusterOrder.indexOf(cluster.clusterId); cluster.polarOrder = order; cluster.SVNextLevelPos = [ - polarOrigin[0] + polarMaxR / 2 * Math.sin(angleStep * order), - polarOrigin[1] + polarMaxR / 2 * Math.cos(angleStep * order) + polarOrigin[0] + polarMaxR / 2 * Math.sin(angleStep2 * order), + polarOrigin[1] + polarMaxR / 2 * Math.cos(angleStep2 * order) ]; cluster.SVNextLevelTran = [ cluster.SVNextLevelPos[0] - cluster.SVPolyCentroid[0], @@ -16741,7 +16771,7 @@ ${indentData}`); }); this.clusterId2cluster = clusterId2cluster; resolve(); - }, this.voronoiForceTime / 2); + }); }); } @@ -16800,7 +16830,6 @@ ${indentData}`); // federjs/FederView/IvfflatView/layout/searchViewLayout.js function searchViewLayoutHandler2({ searchRes }) { const SVCoarsePromise = new Promise((resolve) => __async(this, null, function* () { - this.overviewInitPromise && (yield this.overviewInitPromise); this.searchRes = searchRes; searchRes.coarse.forEach(({ id: id2, dis }) => this.clusters[id2].dis = dis); this.nprobeClusters = this.clusters.filter((cluster) => this.searchRes.csResIds.indexOf(cluster.clusterId) >= 0); @@ -17698,6 +17727,7 @@ ${indentData}`); } searchViewHandler(_0) { return __async(this, arguments, function* ({ searchRes }) { + this.overviewInitPromise && (yield this.overviewInitPromise); this.nprobe = searchRes.csResIds.length; this.k = searchRes.fsResIds.length; this.colorScheme = range(this.nprobe).map((i) => hsl(360 * i / this.nprobe, 1, 0.5).hex()); @@ -17706,7 +17736,7 @@ ${indentData}`); }).then(() => { }); yield this.searchViewInitPromise; - console.log("searchViewHandler finished"); + console.log("searchView Layout finished"); }); } renderSearchView() { diff --git a/test/test.js b/test/test.js index e295a2b..c311cf9 100644 --- a/test/test.js +++ b/test/test.js @@ -112,5 +112,5 @@ window.addEventListener('DOMContentLoaded', async () => { ef: 10, }); feder.searchRandTestVec(); - // feder.searchById(8073); + // feder.searchById(9484); }); From 1f8bace7b4aad126319813ad586a6d3c6fe3a0d5 Mon Sep 17 00:00:00 2001 From: "min.tian" Date: Mon, 6 Jun 2022 09:20:52 +0800 Subject: [PATCH 2/4] optimize the voronoi layout of ivf_flat Signed-off-by: min.tian --- federjs/FederView/IvfflatView/index.js | 1 + .../layout/SVCoarseVoronoiHandler.js | 4 +- .../IvfflatView/layout/SVFinePolarHandler.js | 108 +++++++++--------- .../IvfflatView/layout/overviewLayout.js | 2 +- test/bundle.js | 49 ++++---- test/test.js | 2 +- 6 files changed, 83 insertions(+), 83 deletions(-) diff --git a/federjs/FederView/IvfflatView/index.js b/federjs/FederView/IvfflatView/index.js index ee6e764..aed8a56 100644 --- a/federjs/FederView/IvfflatView/index.js +++ b/federjs/FederView/IvfflatView/index.js @@ -37,6 +37,7 @@ const defaultIvfflatViewParams = { animateExitTime: 1500, animateEnterTime: 1000, fineSearchNodeTransTime: 1200, + forceIterations: 100, }; export default class IvfflatView extends BaseView { diff --git a/federjs/FederView/IvfflatView/layout/SVCoarseVoronoiHandler.js b/federjs/FederView/IvfflatView/layout/SVCoarseVoronoiHandler.js index 216ed10..3b0ca51 100644 --- a/federjs/FederView/IvfflatView/layout/SVCoarseVoronoiHandler.js +++ b/federjs/FederView/IvfflatView/layout/SVCoarseVoronoiHandler.js @@ -51,13 +51,13 @@ export default function SVCoarseVoronoiHandler() { const simulation = d3 .forceSimulation(clusters) - .alphaDecay(1 - Math.pow(0.001, 1 / 50)) + .alphaDecay(1 - Math.pow(0.001, 1 / this.forceIterations / 2)) .force( 'links', d3 .forceLink(links) .id((cluster) => cluster.clusterId) - .strength((_) => 0.15) + .strength((_) => 0.25) ) .force( 'collision', diff --git a/federjs/FederView/IvfflatView/layout/SVFinePolarHandler.js b/federjs/FederView/IvfflatView/layout/SVFinePolarHandler.js index 53037d1..ffb49fc 100644 --- a/federjs/FederView/IvfflatView/layout/SVFinePolarHandler.js +++ b/federjs/FederView/IvfflatView/layout/SVFinePolarHandler.js @@ -1,63 +1,63 @@ import * as d3 from 'd3'; export default function SVFinePolarHandler() { - const nodes = this.searchRes.fine; - // const { isLeft_coarseLevel } = this.targetNode; - // console.log('isLeft_coarseLevel', isLeft_coarseLevel); - - const polarMaxR = this.polarMaxR; - const polarOrigin = this.polarOrigin; - const r = d3 - .scaleLinear() - .domain([ - d3.min( - nodes.filter((node) => node.dis > 0), - (node) => node.dis - ), - d3.max(nodes, (node) => node.dis) * 0.95, - ]) - .range([polarMaxR * 0.2, polarMaxR]) - .clamp(true); + return new Promise((resolve) => { + const nodes = this.searchRes.fine; - nodes.forEach((node) => { - const cluster = this.clusterId2cluster[node.listId]; - const { polarOrder, SVNextLevelPos } = cluster; - node.polarOrder = polarOrder; - let randAngle = Math.random() * Math.PI * 2; - let randBias = [Math.sin, Math.cos].map( - (f) => cluster.r * Math.random() * 0.7 * f(randAngle) - ); - node.voronoiPos = SVNextLevelPos.map((d, i) => d + randBias[i]); - node.x = node.voronoiPos[0]; - node.y = node.voronoiPos[1]; - node.r = r(node.dis); - }); + const polarMaxR = this.polarMaxR; + const polarOrigin = this.polarOrigin; + const distances = nodes + .map((node) => node.dis) + .filter((a) => a > 0) + .sort(); + const minDis = distances.length > 0 ? distances[0] : 0; + const maxDis = + distances.length > 0 + ? distances[Math.round((distances.length - 1) * 0.98)] + : 0; + const r = d3 + .scaleLinear() + .domain([minDis, maxDis]) + .range([polarMaxR * 0.2, polarMaxR]) + .clamp(true); - const simulation = d3 - .forceSimulation(nodes) - .force( - 'collide', - d3 - .forceCollide() - .radius((_) => this.nonTopKNodeR * this.canvasScale) - .strength(0.4) - ) - .force('r', d3.forceRadial((node) => node.r, ...polarOrigin).strength(1)); + nodes.forEach((node) => { + const cluster = this.clusterId2cluster[node.listId]; + const { polarOrder, SVNextLevelPos } = cluster; + node.polarOrder = polarOrder; + let randAngle = Math.random() * Math.PI * 2; + let randBias = [Math.sin, Math.cos].map( + (f) => cluster.r * Math.random() * 0.7 * f(randAngle) + ); + node.voronoiPos = SVNextLevelPos.map((d, i) => d + randBias[i]); + node.x = node.voronoiPos[0]; + node.y = node.voronoiPos[1]; + node.r = r(node.dis); + }); - return new Promise((resolve) => { - setTimeout(() => { - simulation.stop(); - nodes.forEach((node) => { - node.polarPos = [node.x, node.y]; + const simulation = d3 + .forceSimulation(nodes) + .alphaDecay(1 - Math.pow(0.001, 1 / this.forceIterations / 2)) + .force( + 'collide', + d3 + .forceCollide() + .radius((_) => this.nonTopKNodeR * this.canvasScale) + .strength(0.4) + ) + .force('r', d3.forceRadial((node) => node.r, ...polarOrigin).strength(1)) + .on('end', () => { + nodes.forEach((node) => { + node.polarPos = [node.x, node.y]; + }); + this.nodes = nodes; + this.topKNodes = this.nodes.filter((node) => + this.searchRes.fsResIds.find((id) => id == node.id) + ); + this.nonTopKNodes = this.nodes.filter( + (node) => !this.searchRes.fsResIds.find((id) => id == node.id) + ); + resolve(); }); - this.nodes = nodes; - this.topKNodes = this.nodes.filter((node) => - this.searchRes.fsResIds.find((id) => id == node.id) - ); - this.nonTopKNodes = this.nodes.filter( - (node) => !this.searchRes.fsResIds.find((id) => id == node.id) - ); - resolve(); - }, this.nodeCollisionForceTime); }); } diff --git a/federjs/FederView/IvfflatView/layout/overviewLayout.js b/federjs/FederView/IvfflatView/layout/overviewLayout.js index d9093ce..182cb2d 100644 --- a/federjs/FederView/IvfflatView/layout/overviewLayout.js +++ b/federjs/FederView/IvfflatView/layout/overviewLayout.js @@ -37,7 +37,7 @@ export default function overviewLayoutHandler({ indexMeta }) { const simulation = d3 .forceSimulation(clusters) - .alphaDecay(1 - Math.pow(0.001, 1 / 100)) + .alphaDecay(1 - Math.pow(0.001, 1 / this.forceIterations)) .force( 'collision', d3.forceCollide().radius((cluster) => cluster.r) diff --git a/test/bundle.js b/test/bundle.js index 310907c..8fdbd96 100644 --- a/test/bundle.js +++ b/test/bundle.js @@ -16420,7 +16420,7 @@ ${indentData}`); cluster.y = y4(cluster.oriProjection[1]); cluster.r = Math.max(this.minVoronoiRadius * this.canvasScale, Math.sqrt(cluster.countArea / Math.PI)); }); - const simulation = simulation_default(clusters).alphaDecay(1 - Math.pow(1e-3, 1 / 100)).force("collision", collide_default().radius((cluster) => cluster.r)).force("center", center_default(width / 2, height / 2)).on("tick", () => { + const simulation = simulation_default(clusters).alphaDecay(1 - Math.pow(1e-3, 1 / this.forceIterations)).force("collision", collide_default().radius((cluster) => cluster.r)).force("center", center_default(width / 2, height / 2)).on("tick", () => { clusters.forEach((cluster) => { cluster.x = Math.max(cluster.r, Math.min(width - cluster.r, cluster.x)); cluster.y = Math.max(cluster.r, Math.min(height - cluster.r, cluster.y)); @@ -16713,7 +16713,7 @@ ${indentData}`); cluster.x = targetClusterX + biasR * Math.sin(angleStep * i); cluster.y = targetClusterY + biasR * Math.cos(angleStep * i); }); - const simulation = simulation_default(clusters).alphaDecay(1 - Math.pow(1e-3, 1 / 50)).force("links", link_default(links).id((cluster) => cluster.clusterId).strength((_) => 0.15)).force("collision", collide_default().radius((cluster) => cluster.r).strength(0.1)).force("center", center_default(width / 2, height / 2)).on("tick", () => { + const simulation = simulation_default(clusters).alphaDecay(1 - Math.pow(1e-3, 1 / this.forceIterations / 2)).force("links", link_default(links).id((cluster) => cluster.clusterId).strength((_) => 0.25)).force("collision", collide_default().radius((cluster) => cluster.r).strength(0.1)).force("center", center_default(width / 2, height / 2)).on("tick", () => { clusters.forEach((cluster) => { cluster.x = Math.max(cluster.r, Math.min(width - cluster.r, cluster.x)); cluster.y = Math.max(cluster.r, Math.min(height - cluster.r, cluster.y)); @@ -16777,28 +16777,26 @@ ${indentData}`); // federjs/FederView/IvfflatView/layout/SVFinePolarHandler.js function SVFinePolarHandler() { - const nodes = this.searchRes.fine; - const polarMaxR = this.polarMaxR; - const polarOrigin = this.polarOrigin; - const r = linear2().domain([ - min(nodes.filter((node) => node.dis > 0), (node) => node.dis), - max(nodes, (node) => node.dis) * 0.95 - ]).range([polarMaxR * 0.2, polarMaxR]).clamp(true); - nodes.forEach((node) => { - const cluster = this.clusterId2cluster[node.listId]; - const { polarOrder, SVNextLevelPos } = cluster; - node.polarOrder = polarOrder; - let randAngle = Math.random() * Math.PI * 2; - let randBias = [Math.sin, Math.cos].map((f) => cluster.r * Math.random() * 0.7 * f(randAngle)); - node.voronoiPos = SVNextLevelPos.map((d, i) => d + randBias[i]); - node.x = node.voronoiPos[0]; - node.y = node.voronoiPos[1]; - node.r = r(node.dis); - }); - const simulation = simulation_default(nodes).force("collide", collide_default().radius((_) => this.nonTopKNodeR * this.canvasScale).strength(0.4)).force("r", radial_default((node) => node.r, ...polarOrigin).strength(1)); return new Promise((resolve) => { - setTimeout(() => { - simulation.stop(); + const nodes = this.searchRes.fine; + const polarMaxR = this.polarMaxR; + const polarOrigin = this.polarOrigin; + const distances = nodes.map((node) => node.dis).filter((a2) => a2 > 0).sort(); + const minDis = distances.length > 0 ? distances[0] : 0; + const maxDis = distances.length > 0 ? distances[Math.round((distances.length - 1) * 0.98)] : 0; + const r = linear2().domain([minDis, maxDis]).range([polarMaxR * 0.2, polarMaxR]).clamp(true); + nodes.forEach((node) => { + const cluster = this.clusterId2cluster[node.listId]; + const { polarOrder, SVNextLevelPos } = cluster; + node.polarOrder = polarOrder; + let randAngle = Math.random() * Math.PI * 2; + let randBias = [Math.sin, Math.cos].map((f) => cluster.r * Math.random() * 0.7 * f(randAngle)); + node.voronoiPos = SVNextLevelPos.map((d, i) => d + randBias[i]); + node.x = node.voronoiPos[0]; + node.y = node.voronoiPos[1]; + node.r = r(node.dis); + }); + const simulation = simulation_default(nodes).alphaDecay(1 - Math.pow(1e-3, 1 / this.forceIterations / 2)).force("collide", collide_default().radius((_) => this.nonTopKNodeR * this.canvasScale).strength(0.4)).force("r", radial_default((node) => node.r, ...polarOrigin).strength(1)).on("end", () => { nodes.forEach((node) => { node.polarPos = [node.x, node.y]; }); @@ -16806,7 +16804,7 @@ ${indentData}`); this.topKNodes = this.nodes.filter((node) => this.searchRes.fsResIds.find((id2) => id2 == node.id)); this.nonTopKNodes = this.nodes.filter((node) => !this.searchRes.fsResIds.find((id2) => id2 == node.id)); resolve(); - }, this.nodeCollisionForceTime); + }); }); } @@ -17689,7 +17687,8 @@ ${indentData}`); ease: cubicInOut, animateExitTime: 1500, animateEnterTime: 1e3, - fineSearchNodeTransTime: 1200 + fineSearchNodeTransTime: 1200, + forceIterations: 100 }; var IvfflatView = class extends BaseView { constructor({ indexMeta, domSelector: domSelector2, viewParams }) { diff --git a/test/test.js b/test/test.js index c311cf9..91110d7 100644 --- a/test/test.js +++ b/test/test.js @@ -112,5 +112,5 @@ window.addEventListener('DOMContentLoaded', async () => { ef: 10, }); feder.searchRandTestVec(); - // feder.searchById(9484); + // feder.searchById(4365); }); From 91551aa2b72a7d9e1f8b231e3b8712bcaa4553b5 Mon Sep 17 00:00:00 2001 From: "min.tian" Date: Mon, 6 Jun 2022 09:21:55 +0800 Subject: [PATCH 3/4] .. Signed-off-by: min.tian --- federjs/FederView/IvfflatView/index.js | 4 ++-- test/bundle.js | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/federjs/FederView/IvfflatView/index.js b/federjs/FederView/IvfflatView/index.js index aed8a56..f9650b8 100644 --- a/federjs/FederView/IvfflatView/index.js +++ b/federjs/FederView/IvfflatView/index.js @@ -14,8 +14,8 @@ import InfoPanel from './InfoPanel'; const defaultIvfflatViewParams = { minVoronoiRadius: 4, - voronoiForceTime: 3000, - nodeCollisionForceTime: 1000, + // voronoiForceTime: 3000, + // nodeCollisionForceTime: 1000, backgroundColor: 'red', voronoiStrokeWidth: 2, targetNodeStrokeWidth: 5, diff --git a/test/bundle.js b/test/bundle.js index 8fdbd96..29a9ba8 100644 --- a/test/bundle.js +++ b/test/bundle.js @@ -17665,8 +17665,6 @@ ${indentData}`); // federjs/FederView/IvfflatView/index.js var defaultIvfflatViewParams = { minVoronoiRadius: 4, - voronoiForceTime: 3e3, - nodeCollisionForceTime: 1e3, backgroundColor: "red", voronoiStrokeWidth: 2, targetNodeStrokeWidth: 5, From a219c4997b0a483aaa5b77c5a8560870ba004f90 Mon Sep 17 00:00:00 2001 From: "min.tian" Date: Mon, 6 Jun 2022 11:18:23 +0800 Subject: [PATCH 4/4] get the node of feder (html-element) Signed-off-by: min.tian --- federjs/Feder.js | 5 +- federjs/FederView/BaseView.js | 12 +-- federjs/FederView/HnswView/InfoPanel/index.js | 11 ++- federjs/FederView/HnswView/index.js | 8 +- .../FederView/IvfflatView/InfoPanel/index.js | 12 +-- federjs/FederView/IvfflatView/index.js | 6 +- federjs/FederView/index.js | 13 ++- federjs/FederView/loading.js | 14 ++-- package.json | 2 +- test/bundle.js | 79 ++++++++++--------- test/test.js | 2 + 11 files changed, 89 insertions(+), 75 deletions(-) diff --git a/federjs/Feder.js b/federjs/Feder.js index 5a31740..0cdc235 100644 --- a/federjs/Feder.js +++ b/federjs/Feder.js @@ -5,7 +5,7 @@ export default class Feder { core = null, filePath = '', source = '', - domSelector, + domSelector = null, viewParams = {}, }) { this.federView = new FederView({ domSelector, viewParams }); @@ -31,6 +31,9 @@ export default class Feder { } } + get node() { + return this.federView.dom; + } async overview() { this.initCorePromise && (await this.initCorePromise); this.federView.overview(); diff --git a/federjs/FederView/BaseView.js b/federjs/FederView/BaseView.js index f1b3cf0..8041bd6 100644 --- a/federjs/FederView/BaseView.js +++ b/federjs/FederView/BaseView.js @@ -3,8 +3,8 @@ import { renderLoading, finishLoading } from './loading'; import { VIEW_TYPE } from 'Types'; export default class BaseView { - constructor({ domSelector, viewParams, getVectorById }) { - this.domSelector = domSelector; + constructor({ dom, viewParams, getVectorById }) { + this.dom = dom; this.viewParams = viewParams; const { width, height, canvasScale, mediaType, mediaCallback } = viewParams; @@ -18,8 +18,8 @@ export default class BaseView { this.mediaCallback = mediaCallback; } initCanvas() { - renderLoading(this.domSelector); - const dom = d3.select(this.domSelector); + renderLoading(this.dom, this.viewParams.width, this.viewParams.height); + const dom = d3.select(this.dom); dom.selectAll('canvas').remove(); const canvas = dom .append('canvas') @@ -43,7 +43,7 @@ export default class BaseView { this.clickedNode = null; this.hoveredNode = null; this.overviewInitPromise && (await this.overviewInitPromise); - finishLoading(this.domSelector); + finishLoading(this.dom); this.renderOverview(); this.addMouseListener(); this.setOverviewListenerHandlers(); @@ -55,7 +55,7 @@ export default class BaseView { this.clickedNode = null; this.hoveredNode = null; await this.searchViewHandler({ searchRes }); - finishLoading(this.domSelector); + finishLoading(this.dom); this.renderSearchView(); this.addMouseListener(); this.setSearchViewListenerHandlers(); diff --git a/federjs/FederView/HnswView/InfoPanel/index.js b/federjs/FederView/HnswView/InfoPanel/index.js index a3c8b72..8ffab47 100644 --- a/federjs/FederView/HnswView/InfoPanel/index.js +++ b/federjs/FederView/HnswView/InfoPanel/index.js @@ -19,11 +19,10 @@ export const hoveredPanelId = 'feder-info-hovered-panel'; const panelBackgroundColor = hexWithOpacity(blackColor, 0.6); export default class InfoPanel { - constructor({ domSelector, width, height }) { - this.domSelector = domSelector; + constructor({ dom, width, height }) { + this.dom = dom; this.width = width; this.height = height; - const dom = document.querySelector(domSelector); const overviewPanel = document.createElement('div'); overviewPanel.setAttribute('id', overviewPanelId); @@ -131,7 +130,7 @@ export default class InfoPanel { } renderSelectedPanel(itemList = [], color = '#000') { - const panel = d3.select(this.domSelector).select(`#${selectedPanelId}`); + const panel = d3.select(this.dom).select(`#${selectedPanelId}`); panel.style('color', color); if (itemList.length === 0) panel.classed('hide', true); else { @@ -146,7 +145,7 @@ export default class InfoPanel { y = 0, isLeft = false, } = {}) { - const panel = d3.select(this.domSelector).select(`#${hoveredPanelId}`); + const panel = d3.select(this.dom).select(`#${hoveredPanelId}`); if (itemList.length === 0) panel.classed('hide', true); else { panel.style('color', color); @@ -168,7 +167,7 @@ export default class InfoPanel { } } renderOverviewPanel(itemList = [], color) { - const panel = d3.select(this.domSelector).select(`#${overviewPanelId}`); + const panel = d3.select(this.dom).select(`#${overviewPanelId}`); panel.style('color', color); if (itemList.length === 0) panel.classed('hide', true); else { diff --git a/federjs/FederView/HnswView/index.js b/federjs/FederView/HnswView/index.js index b993e9e..eed661b 100644 --- a/federjs/FederView/HnswView/index.js +++ b/federjs/FederView/HnswView/index.js @@ -32,10 +32,10 @@ const defaultHnswViewParams = { hoveredPanelLineWidth: 2, }; export default class HnswView extends BaseView { - constructor({ indexMeta, domSelector, viewParams, getVectorById }) { + constructor({ indexMeta, dom, viewParams, getVectorById }) { super({ indexMeta, - domSelector, + dom, viewParams, getVectorById, }); @@ -45,7 +45,7 @@ export default class HnswView extends BaseView { } this.padding = this.padding.map((num) => num * this.canvasScale); const infoPanel = new InfoPanel({ - domSelector, + dom, width: this.width, height: this.height, }); @@ -106,7 +106,7 @@ export default class HnswView extends BaseView { this.updateSearchViewOverviewInfo({}); } renderSearchView() { - const timeControllerView = new TimeControllerView(this.domSelector); + const timeControllerView = new TimeControllerView(this.dom); const callback = ({ t, p }) => { renderSearchViewTransition.call(this, { t, p }); diff --git a/federjs/FederView/IvfflatView/InfoPanel/index.js b/federjs/FederView/IvfflatView/InfoPanel/index.js index c16be22..3d80e89 100644 --- a/federjs/FederView/IvfflatView/InfoPanel/index.js +++ b/federjs/FederView/IvfflatView/InfoPanel/index.js @@ -17,8 +17,8 @@ export const hoveredPanelId = 'feder-info-hovered-panel'; const panelBackgroundColor = hexWithOpacity(blackColor, 0.6); export default class InfoPanel { - constructor({ domSelector, width, height }) { - this.domSelector = domSelector; + constructor({ dom, width, height }) { + this.dom = dom; this.width = width; this.height = height; @@ -27,7 +27,7 @@ export default class InfoPanel { this.initStyle(); } initOverviewPanel() { - const dom = document.querySelector(this.domSelector); + const dom = this.dom; const overviewPanel = document.createElement('div'); overviewPanel.setAttribute('id', overviewPanelId); overviewPanel.className = @@ -222,7 +222,7 @@ export default class InfoPanel { } initHoveredPanel() { - const dom = document.querySelector(this.domSelector); + const dom = this.dom; const hoveredPanel = document.createElement('div'); hoveredPanel.setAttribute('id', hoveredPanelId); hoveredPanel.className = hoveredPanel.className + 'panel-border panel hide'; @@ -416,7 +416,7 @@ export default class InfoPanel { document.getElementsByTagName('head').item(0).appendChild(style); } renderHoveredPanel(itemList = [], color = '#000', x = 0, y = 0) { - const panel = d3.select(this.domSelector).select(`#${hoveredPanelId}`); + const panel = d3.select(this.dom).select(`#${hoveredPanelId}`); if (itemList.length === 0) panel.classed('hide', true); else { panel.style('color', color); @@ -444,7 +444,7 @@ export default class InfoPanel { } } renderOverviewPanel(itemList = [], color) { - const panel = d3.select(this.domSelector).select(`#${overviewPanelId}`); + const panel = d3.select(this.dom).select(`#${overviewPanelId}`); panel.style('color', color); if (itemList.length === 0) panel.classed('hide', true); else { diff --git a/federjs/FederView/IvfflatView/index.js b/federjs/FederView/IvfflatView/index.js index f9650b8..5bf56ab 100644 --- a/federjs/FederView/IvfflatView/index.js +++ b/federjs/FederView/IvfflatView/index.js @@ -41,10 +41,10 @@ const defaultIvfflatViewParams = { }; export default class IvfflatView extends BaseView { - constructor({ indexMeta, domSelector, viewParams }) { + constructor({ indexMeta, dom, viewParams }) { super({ indexMeta, - domSelector, + dom, viewParams, }); for (let key in defaultIvfflatViewParams) { @@ -59,7 +59,7 @@ export default class IvfflatView extends BaseView { this.overviewHandler({ indexMeta }); this.infoPanel = new InfoPanel({ - domSelector, + dom, width: viewParams.width, height: viewParams.height, }); diff --git a/federjs/FederView/index.js b/federjs/FederView/index.js index 6ca6c56..122a0f7 100644 --- a/federjs/FederView/index.js +++ b/federjs/FederView/index.js @@ -24,8 +24,8 @@ export default class FederView { this.initDom(); } initDom() { - const dom = document.querySelector(this.domSelector); - dom.innerHTML = ''; + const dom = document.createElement('div'); + this.dom = dom; const { width, height } = this.viewParams; const domStyle = { position: 'relative', @@ -36,13 +36,18 @@ export default class FederView { }; Object.assign(dom.style, domStyle); initLoadingStyle(); - renderLoading(this.domSelector); + renderLoading(this.dom, width, height); + + if (this.domSelector) { + const domContainer = document.querySelector(this.domSelector); + domContainer.appendChild(dom); + } } initView({ indexType, indexMeta, getVectorById }) { if (indexType in viewHandlerMap) { this.view = new viewHandlerMap[indexType]({ indexMeta, - domSelector: this.domSelector, + dom: this.dom, viewParams: this.viewParams, getVectorById, }); diff --git a/federjs/FederView/loading.js b/federjs/FederView/loading.js index 6cadab6..3926aa7 100644 --- a/federjs/FederView/loading.js +++ b/federjs/FederView/loading.js @@ -27,10 +27,10 @@ export const initLoadingStyle = () => { document.getElementsByTagName('head').item(0).appendChild(style); }; -export const renderLoading = (domSelector) => { - const dom = d3.select(domSelector); - const { width, height } = dom.node().getBoundingClientRect(); - if (!d3.select(`#${loadingSvgId}`).empty()) return; +export const renderLoading = (domNode, width, height) => { + const dom = d3.select(domNode); + // const { width, height } = dom.node().getBoundingClientRect(); + if (!dom.select(`#${loadingSvgId}`).empty()) return; const svg = dom .append('svg') .attr('id', loadingSvgId) @@ -83,7 +83,7 @@ export const renderLoading = (domSelector) => { .classed('rotate', true); }; -export const finishLoading = (domSelector) => { - const dom = d3.select(domSelector); - dom.select(`#${loadingSvgId}`).remove(); +export const finishLoading = (domNode) => { + const dom = d3.select(domNode); + dom.selectAll(`#${loadingSvgId}`).remove(); }; diff --git a/package.json b/package.json index 7eacdc2..09a7686 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@zilliz/feder", "author": "ued@zilliz.com", - "version": "0.1.10", + "version": "0.1.11", "description": "visualization packages for vector space", "main": "dist/index.js", "files": [ diff --git a/test/bundle.js b/test/bundle.js index 29a9ba8..ea4843f 100644 --- a/test/bundle.js +++ b/test/bundle.js @@ -14360,10 +14360,9 @@ ${indentData}`); `; document.getElementsByTagName("head").item(0).appendChild(style); }; - var renderLoading = (domSelector2) => { - const dom = select_default2(domSelector2); - const { width, height } = dom.node().getBoundingClientRect(); - if (!select_default2(`#${loadingSvgId2}`).empty()) + var renderLoading = (domNode, width, height) => { + const dom = select_default2(domNode); + if (!dom.select(`#${loadingSvgId2}`).empty()) return; const svg = dom.append("svg").attr("id", loadingSvgId2).attr("width", loadingWidth2).attr("height", loadingWidth2).style("position", "absolute").style("left", width / 2 - loadingWidth2 / 2).style("bottom", height / 2 - loadingWidth2 / 2).style("overflow", "visible"); const defsG = svg.append("defs"); @@ -14374,15 +14373,15 @@ ${indentData}`); const loadingCircle = svg.append("circle").attr("cx", loadingWidth2 / 2).attr("cy", loadingWidth2 / 2).attr("fill", "none").attr("r", loadingWidth2 / 2).attr("stroke", "#1E64FF").attr("stroke-width", loadingStrokeWidth); const semiCircle = svg.append("path").attr("d", `M0,${-loadingWidth2 / 2} a ${loadingWidth2 / 2} ${loadingWidth2 / 2} 0 1 1 ${0} ${loadingWidth2}`).attr("fill", "none").attr("stroke", `url(#${linearGradientId})`).attr("stroke-width", loadingStrokeWidth).classed("rotate", true); }; - var finishLoading = (domSelector2) => { - const dom = select_default2(domSelector2); - dom.select(`#${loadingSvgId2}`).remove(); + var finishLoading = (domNode) => { + const dom = select_default2(domNode); + dom.selectAll(`#${loadingSvgId2}`).remove(); }; // federjs/FederView/BaseView.js var BaseView = class { - constructor({ domSelector: domSelector2, viewParams, getVectorById }) { - this.domSelector = domSelector2; + constructor({ dom, viewParams, getVectorById }) { + this.dom = dom; this.viewParams = viewParams; const { width, height, canvasScale, mediaType, mediaCallback } = viewParams; this.clientWidth = width; @@ -14395,8 +14394,8 @@ ${indentData}`); this.mediaCallback = mediaCallback; } initCanvas() { - renderLoading(this.domSelector); - const dom = select_default2(this.domSelector); + renderLoading(this.dom, this.viewParams.width, this.viewParams.height); + const dom = select_default2(this.dom); dom.selectAll("canvas").remove(); const canvas = dom.append("canvas").attr("width", this.clientWidth).attr("height", this.clientHeight); const ctx = canvas.node().getContext("2d"); @@ -14421,7 +14420,7 @@ ${indentData}`); this.clickedNode = null; this.hoveredNode = null; this.overviewInitPromise && (yield this.overviewInitPromise); - finishLoading(this.domSelector); + finishLoading(this.dom); this.renderOverview(); this.addMouseListener(); this.setOverviewListenerHandlers(); @@ -14435,7 +14434,7 @@ ${indentData}`); this.clickedNode = null; this.hoveredNode = null; yield this.searchViewHandler({ searchRes }); - finishLoading(this.domSelector); + finishLoading(this.dom); this.renderSearchView(); this.addMouseListener(); this.setSearchViewListenerHandlers(); @@ -15785,11 +15784,10 @@ ${indentData}`); var hoveredPanelId = "feder-info-hovered-panel"; var panelBackgroundColor = hexWithOpacity(blackColor, 0.6); var InfoPanel = class { - constructor({ domSelector: domSelector2, width, height }) { - this.domSelector = domSelector2; + constructor({ dom, width, height }) { + this.dom = dom; this.width = width; this.height = height; - const dom = document.querySelector(domSelector2); const overviewPanel = document.createElement("div"); overviewPanel.setAttribute("id", overviewPanelId); overviewPanel.className = overviewPanel.className + " panel-border panel hide"; @@ -15886,7 +15884,7 @@ ${indentData}`); document.getElementsByTagName("head").item(0).appendChild(style); } renderSelectedPanel(itemList = [], color2 = "#000") { - const panel = select_default2(this.domSelector).select(`#${selectedPanelId}`); + const panel = select_default2(this.dom).select(`#${selectedPanelId}`); panel.style("color", color2); if (itemList.length === 0) panel.classed("hide", true); @@ -15902,7 +15900,7 @@ ${indentData}`); y: y4 = 0, isLeft = false } = {}) { - const panel = select_default2(this.domSelector).select(`#${hoveredPanelId}`); + const panel = select_default2(this.dom).select(`#${hoveredPanelId}`); if (itemList.length === 0) panel.classed("hide", true); else { @@ -15921,7 +15919,7 @@ ${indentData}`); } } renderOverviewPanel(itemList = [], color2) { - const panel = select_default2(this.domSelector).select(`#${overviewPanelId}`); + const panel = select_default2(this.dom).select(`#${overviewPanelId}`); panel.style("color", color2); if (itemList.length === 0) panel.classed("hide", true); @@ -16215,10 +16213,10 @@ ${indentData}`); hoveredPanelLineWidth: 2 }; var HnswView = class extends BaseView { - constructor({ indexMeta, domSelector: domSelector2, viewParams, getVectorById }) { + constructor({ indexMeta, dom, viewParams, getVectorById }) { super({ indexMeta, - domSelector: domSelector2, + dom, viewParams, getVectorById }); @@ -16227,7 +16225,7 @@ ${indentData}`); } this.padding = this.padding.map((num) => num * this.canvasScale); const infoPanel = new InfoPanel({ - domSelector: domSelector2, + dom, width: this.width, height: this.height }); @@ -16272,7 +16270,7 @@ ${indentData}`); }); } renderSearchView() { - const timeControllerView = new TimeControllerView_default(this.domSelector); + const timeControllerView = new TimeControllerView_default(this.dom); const callback = ({ t, p }) => { renderSearchViewTransition.call(this, { t, p }); timeControllerView.moveSilderBar(p); @@ -17226,8 +17224,8 @@ ${indentData}`); var hoveredPanelId2 = "feder-info-hovered-panel"; var panelBackgroundColor2 = hexWithOpacity(blackColor, 0.6); var InfoPanel2 = class { - constructor({ domSelector: domSelector2, width, height }) { - this.domSelector = domSelector2; + constructor({ dom, width, height }) { + this.dom = dom; this.width = width; this.height = height; this.initOverviewPanel(); @@ -17235,7 +17233,7 @@ ${indentData}`); this.initStyle(); } initOverviewPanel() { - const dom = document.querySelector(this.domSelector); + const dom = this.dom; const overviewPanel = document.createElement("div"); overviewPanel.setAttribute("id", overviewPanelId2); overviewPanel.className = overviewPanel.className + " panel-border panel hide"; @@ -17404,7 +17402,7 @@ ${indentData}`); this.renderOverviewPanel(items, whiteColor); } initHoveredPanel() { - const dom = document.querySelector(this.domSelector); + const dom = this.dom; const hoveredPanel = document.createElement("div"); hoveredPanel.setAttribute("id", hoveredPanelId2); hoveredPanel.className = hoveredPanel.className + "panel-border panel hide"; @@ -17578,7 +17576,7 @@ ${indentData}`); document.getElementsByTagName("head").item(0).appendChild(style); } renderHoveredPanel(itemList = [], color2 = "#000", x3 = 0, y4 = 0) { - const panel = select_default2(this.domSelector).select(`#${hoveredPanelId2}`); + const panel = select_default2(this.dom).select(`#${hoveredPanelId2}`); if (itemList.length === 0) panel.classed("hide", true); else { @@ -17603,7 +17601,7 @@ ${indentData}`); } } renderOverviewPanel(itemList = [], color2) { - const panel = select_default2(this.domSelector).select(`#${overviewPanelId2}`); + const panel = select_default2(this.dom).select(`#${overviewPanelId2}`); panel.style("color", color2); if (itemList.length === 0) panel.classed("hide", true); @@ -17689,10 +17687,10 @@ ${indentData}`); forceIterations: 100 }; var IvfflatView = class extends BaseView { - constructor({ indexMeta, domSelector: domSelector2, viewParams }) { + constructor({ indexMeta, dom, viewParams }) { super({ indexMeta, - domSelector: domSelector2, + dom, viewParams }); for (let key in defaultIvfflatViewParams) { @@ -17701,7 +17699,7 @@ ${indentData}`); this.projectPadding = this.projectPadding.map((num) => num * this.canvasScale); this.overviewHandler({ indexMeta }); this.infoPanel = new InfoPanel2({ - domSelector: domSelector2, + dom, width: viewParams.width, height: viewParams.height }); @@ -17881,8 +17879,8 @@ ${indentData}`); this.initDom(); } initDom() { - const dom = document.querySelector(this.domSelector); - dom.innerHTML = ""; + const dom = document.createElement("div"); + this.dom = dom; const { width, height } = this.viewParams; const domStyle = { position: "relative", @@ -17891,13 +17889,17 @@ ${indentData}`); }; Object.assign(dom.style, domStyle); initLoadingStyle(); - renderLoading(this.domSelector); + renderLoading(this.dom, width, height); + if (this.domSelector) { + const domContainer = document.querySelector(this.domSelector); + domContainer.appendChild(dom); + } } initView({ indexType, indexMeta, getVectorById }) { if (indexType in viewHandlerMap) { this.view = new viewHandlerMap[indexType]({ indexMeta, - domSelector: this.domSelector, + dom: this.dom, viewParams: this.viewParams, getVectorById }); @@ -17925,7 +17927,7 @@ ${indentData}`); core = null, filePath = "", source = "", - domSelector: domSelector2, + domSelector: domSelector2 = null, viewParams = {} }) { this.federView = new FederView({ domSelector: domSelector2, viewParams }); @@ -17946,6 +17948,9 @@ ${indentData}`); } else { } } + get node() { + return this.federView.dom; + } overview() { return __async(this, null, function* () { this.initCorePromise && (yield this.initCorePromise); diff --git a/test/test.js b/test/test.js index 91110d7..8834414 100644 --- a/test/test.js +++ b/test/test.js @@ -113,4 +113,6 @@ window.addEventListener('DOMContentLoaded', async () => { }); feder.searchRandTestVec(); // feder.searchById(4365); + + // document.querySelector(domSelector).appendChild(feder.node); });