Skip to content

Commit 22c19bd

Browse files
authored
Merge pull request #68 from zilliztech/federTest
Improve code structure
2 parents 5a5b6ad + f9d34d9 commit 22c19bd

20 files changed

+302
-353
lines changed

dev/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@ window.addEventListener('DOMContentLoaded', async () => {
1515
const testSearchParams = {
1616
k: 4,
1717
ef: 6,
18-
nprobe: 4,
18+
nprobe: 6,
1919
};
2020

2121
const mediaCallback = await getRowId2imgUrl();
2222
const viewParams = {
2323
width: 800,
2424
height: 480,
25+
canvasScale: 1,
2526
projectParams: { projectSeed: 12315 },
2627
mediaType: 'img',
2728
mediaCallback,

federjs/FederLayout/visDataHandler/hnsw/defaultLayoutParamsHnsw.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { TLayoutParamsHnsw } from 'Types/visData';
33
export const defaultHnswLayoutParams = {
44
width: 800,
55
height: 480,
6-
canvasScale: 2,
6+
canvasScale: 1,
77
targetOrigin: [0, 0],
88
numForceIterations: 100,
99
padding: [80, 200, 60, 220],
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { EProjectMethod } from "Types";
2+
3+
export const defaultLayoutParamsIvfflat = {
4+
numForceIterations: 100,
5+
width: 800,
6+
height: 480,
7+
canvasScale: 1,
8+
coarseSearchWithProjection: true,
9+
fineSearchWithProjection: true,
10+
projectMethod: EProjectMethod.umap,
11+
projectParams: {},
12+
polarOriginBias: 0.15,
13+
polarRadiusUpperBound: 0.97,
14+
nonTopkNodeR: 3,
15+
minVoronoiRadius: 5,
16+
projectPadding: [20, 30, 20, 30],
17+
staticPanelWidth: 240,
18+
};

federjs/FederLayout/visDataHandler/ivfflat/index.ts

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { EProjectMethod, EViewType } from 'Types';
1+
import { EViewType } from 'Types';
22
import { TIndexMetaIvfflat } from 'Types/indexMeta';
33
import { TSearchRecords } from 'Types/searchRecords';
44
import {
@@ -8,6 +8,7 @@ import {
88
TVisDataIvfflatSearchView,
99
} from 'Types/visData';
1010
import { TFederLayoutHandler } from '../../FederLayoutHandler';
11+
import { defaultLayoutParamsIvfflat } from './defaultLayoutParamsIvfflat';
1112
import IvfflatOverviewLayout from './overview';
1213
import IvfflatSearchViewLayout from './search';
1314

@@ -19,22 +20,6 @@ const searchViewLayoutFuncMap = {
1920
[EViewType.default]: IvfflatSearchViewLayout,
2021
};
2122

22-
const layoutParamsIvfflatDefault = {
23-
numForceIterations: 100,
24-
width: 800,
25-
height: 480,
26-
canvasScale: 2,
27-
coarseSearchWithProjection: true,
28-
fineSearchWithProjection: true,
29-
projectMethod: EProjectMethod.umap,
30-
projectParams: {},
31-
polarOriginBias: 0.15,
32-
polarRadiusUpperBound: 0.97,
33-
nonTopkNodeR: 3,
34-
minVoronoiRadius: 5,
35-
projectPadding: [20, 30, 20, 30],
36-
staticPanelWidth: 240,
37-
};
3823
export default class FederLayoutIvfflat implements TFederLayoutHandler {
3924
overviewLayoutParams: TLayoutParamsIvfflat = {};
4025
overviewClusters: TVisDataIvfflatOverviewCluster;
@@ -45,7 +30,7 @@ export default class FederLayoutIvfflat implements TFederLayoutHandler {
4530
): Promise<TVisDataIvfflatOverview> {
4631
const layoutParams = Object.assign(
4732
{},
48-
layoutParamsIvfflatDefault,
33+
defaultLayoutParamsIvfflat,
4934
_layoutParams
5035
);
5136
this.overviewLayoutParams = layoutParams;
@@ -63,7 +48,7 @@ export default class FederLayoutIvfflat implements TFederLayoutHandler {
6348
): Promise<TVisDataIvfflatSearchView> {
6449
const layoutParams = Object.assign(
6550
{},
66-
layoutParamsIvfflatDefault,
51+
defaultLayoutParamsIvfflat,
6752
_layoutParams
6853
);
6954
const searchViewLayoutFunc = searchViewLayoutFuncMap[viewType];
Lines changed: 19 additions & 221 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,21 @@
11
import * as d3 from 'd3';
2-
import {
3-
getNodeIdWithLevel,
4-
parseNodeIdWidthLevel,
5-
} from 'FederLayout/visDataHandler/hnsw/utils';
6-
import clearCanvas from 'FederView/clearCanvas';
7-
import InfoPanel, { TInfoPanelContentItem } from 'FederView/InfoPanel';
2+
import InfoPanel from 'FederView/InfoPanel';
3+
import initCanvas from 'FederView/initCanvas';
4+
import initEventListener from 'FederView/initEventListener';
85
import TViewHandler from 'FederView/types';
9-
import { EMediaType, TCoord, TD3Link, TId } from 'Types';
6+
import { TCoord, TId } from 'Types';
107
import {
118
TViewParamsHnsw,
129
TVisDataHnswGraph,
1310
TVisDataHnswGraphNode,
1411
TVisDataHnswOverview,
1512
} from 'Types/visData';
16-
import { getDisL2Square, vecAdd, vecMultiply } from 'Utils/distFunc';
13+
import { getDisL2Square } from 'Utils/distFunc';
1714
import defaultViewParamsHnsw from '../defaultViewParamsHnsw';
18-
import renderLayer from '../HnswSearchView/renderLayer';
1915
import initPanels from '../initPanels';
20-
import renderTipLine from '../renderTipLine';
21-
import renderLinks from './renderLinks';
22-
import renderNodes from './renderNodes';
16+
import updateClickedPanel from './updateClickedPanel';
17+
import updateStaticPanel from './updateStaticPanel';
18+
import renderView from './renderView';
2319
export default class HnswOverview implements TViewHandler {
2420
node: HTMLElement;
2521
staticPanel: InfoPanel;
@@ -59,8 +55,8 @@ export default class HnswOverview implements TViewHandler {
5955
}
6056
init(): void {
6157
this.initIdWithLevel2node();
62-
this.initCanvas();
63-
this.initEventListener();
58+
initCanvas.call(this);
59+
initEventListener.call(this);
6460
initPanels.call(this);
6561
}
6662
initIdWithLevel2node() {
@@ -72,126 +68,14 @@ export default class HnswOverview implements TViewHandler {
7268
);
7369
this.idWithLevel2node = idWithLevel2node;
7470
}
75-
initCanvas() {
76-
const { width, height, canvasScale } = this.viewParams;
77-
const divD3 = d3
78-
.create('div')
79-
.style('width', `${width}px`)
80-
.style('height', `${height}px`)
81-
.style('position', 'relative');
82-
this.node = divD3.node();
83-
const canvasD3 = divD3
84-
.append('canvas')
85-
.attr('width', width)
86-
.attr('height', height);
87-
this.ctx = canvasD3.node().getContext('2d');
88-
this.ctx.scale(1 / canvasScale, 1 / canvasScale);
89-
}
9071
render(): void {
9172
this.initView();
9273
}
93-
async updateStaticPanel() {
94-
this.staticPanel.setContent({
95-
themeColor: '#FFFFFF',
96-
hasBorder: true,
97-
content: [
98-
{
99-
title: 'HNSW',
100-
},
101-
{ text: `M = ${this.M}, ef_construction = ${this.efConstruction}` },
102-
{
103-
text: `${this.ntotal} vectors, ${this.nlevels}-layer hierarchical graph (only visual the top-${this.overviewNodesLevels.length} layers).`,
104-
},
105-
...this.nodesCount
106-
.map((c, level) => {
107-
return {
108-
title: `Level ${level}`,
109-
text: `${c} vectors, ${this.linksCount[level]} links`,
110-
};
111-
})
112-
.reverse(),
113-
],
114-
});
115-
}
116-
async updateClickedPanel() {
117-
const node = this.clickedNode;
118-
if (!node) {
119-
this.clickedPanel.setContent({ content: [] });
120-
return;
121-
}
122-
123-
const mediaContent = {} as TInfoPanelContentItem;
124-
if (this.viewParams.mediaType === EMediaType.image)
125-
mediaContent.image = this.viewParams.mediaContent(node.id);
126-
else if (this.viewParams.mediaType === EMediaType.text)
127-
mediaContent.text = this.viewParams.mediaContent(node.id);
128-
129-
const pathFromEntryTexts = this.overviewNodesLevels
130-
.filter((_, level) => {
131-
return level >= this.clickedLevel;
132-
})
133-
.map(
134-
({ level }) =>
135-
`level ${level}: ` +
136-
node.pathFromEntry
137-
.filter(
138-
(idWithLevel) => parseNodeIdWidthLevel(idWithLevel)[0] === level
139-
)
140-
.map((idWithLevel) => parseNodeIdWidthLevel(idWithLevel)[1])
141-
.join(' => ')
142-
)
143-
.reverse();
144-
145-
const linkedNodeText = node.links.join(', ');
146-
this.clickedPanel.setContent({
147-
themeColor: '#FFFC85',
148-
hasBorder: true,
149-
content: [
150-
{ title: `Level ${node.level}` },
151-
{ title: `Row No. ${node.id}` },
152-
mediaContent,
153-
{ title: `Shortest path from the entry:` },
154-
...pathFromEntryTexts.map((text) => ({ text })),
155-
{ title: `Linked vectors:` },
156-
{ text: linkedNodeText },
157-
],
158-
});
159-
}
160-
async updateHoveredPanel(hoveredPanelPos: TCoord, reverse = false) {
161-
if (!hoveredPanelPos) {
162-
this.hoveredPanel.setContent({ content: [] });
163-
return;
164-
}
165-
if (reverse)
166-
this.hoveredPanel.setPosition({
167-
left: null,
168-
right: `${this.viewParams.width - hoveredPanelPos[0]}px`,
169-
top: `${hoveredPanelPos[1] - 4}px`,
170-
});
171-
else
172-
this.hoveredPanel.setPosition({
173-
left: `${hoveredPanelPos[0]}px`,
174-
top: `${hoveredPanelPos[1] - 4}px`,
175-
});
176-
177-
const mediaContent = {} as TInfoPanelContentItem;
178-
if (this.viewParams.mediaType === EMediaType.image)
179-
mediaContent.image = this.viewParams.mediaContent(this.hoveredNode.id);
180-
else if (this.viewParams.mediaType === EMediaType.text)
181-
mediaContent.text = this.viewParams.mediaContent(this.hoveredNode.id);
18274

183-
this.hoveredPanel.setContent({
184-
themeColor: '#FFFC85',
185-
hasBorder: false,
186-
flex: true,
187-
flexDirection: reverse ? 'row-reverse' : 'row',
188-
content: [{ title: `No. ${this.hoveredNode.id}` }, mediaContent],
189-
});
190-
}
19175
initView() {
19276
// event;
193-
this.renderView();
194-
this.updateStaticPanel();
77+
renderView.call(this);
78+
updateStaticPanel.call(this);
19579

19680
const mouse2level = (x: number, y: number) =>
19781
this.overviewLayerPosLevels.findIndex((points) =>
@@ -214,13 +98,13 @@ export default class HnswOverview implements TViewHandler {
21498
const clickedNode = mouse2node(x, y, this.clickedLevel);
21599
if (clickedNode != this.clickedNode) {
216100
this.clickedNode = clickedNode;
217-
this.renderView();
218-
this.updateClickedPanel();
101+
renderView.call(this);
102+
updateClickedPanel.call(this);
219103
}
220104
} else {
221105
this.clickedNode = null;
222-
this.renderView();
223-
this.updateClickedPanel();
106+
renderView.call(this);
107+
updateClickedPanel.call(this);
224108
}
225109
};
226110
this.mouseMoveHandler = ({ x, y }: { x: number; y: number }) => {
@@ -229,104 +113,18 @@ export default class HnswOverview implements TViewHandler {
229113
const hoveredNode = mouse2node(x, y, this.hoveredLevel);
230114
if (hoveredNode != this.hoveredNode) {
231115
this.hoveredNode = hoveredNode;
232-
this.renderView();
116+
renderView.call(this);
233117
}
234118
} else {
235119
this.hoveredLevel = -1;
236120
this.hoveredNode = null;
237-
this.renderView();
121+
renderView.call(this);
238122
}
239123
};
240124
this.mouseLeaveHandler = () => {
241125
this.hoveredLevel = -1;
242126
this.hoveredNode = null;
243-
this.renderView();
127+
renderView.call(this);
244128
};
245129
}
246-
initEventListener() {
247-
const { canvasScale } = this.viewParams;
248-
this.node.addEventListener('mousemove', (e) => {
249-
const { offsetX, offsetY } = e;
250-
const x = offsetX * canvasScale;
251-
const y = offsetY * canvasScale;
252-
this.mouseMoveHandler && this.mouseMoveHandler({ x, y });
253-
});
254-
this.node.addEventListener('click', (e) => {
255-
const { offsetX, offsetY } = e;
256-
const x = offsetX * canvasScale;
257-
const y = offsetY * canvasScale;
258-
this.mouseClickHandler && this.mouseClickHandler({ x, y });
259-
});
260-
this.node.addEventListener('mouseleave', () => {
261-
this.mouseLeaveHandler && this.mouseLeaveHandler();
262-
});
263-
}
264-
renderView() {
265-
clearCanvas.call(this);
266-
267-
const highlightNode = this.clickedNode || this.hoveredNode;
268-
269-
for (let i = 0; i < this.overviewNodesLevels.length; i++) {
270-
const { nodes, level } = this.overviewNodesLevels[i];
271-
272-
const baseLinks =
273-
i > 1
274-
? nodes.reduce(
275-
(acc, node) =>
276-
acc.concat(
277-
node.links.map((targetId) => ({
278-
source: getNodeIdWithLevel(node.id, level),
279-
target: getNodeIdWithLevel(targetId, level),
280-
}))
281-
),
282-
[] as TD3Link[]
283-
)
284-
: [];
285-
const pathFromEntryLinks =
286-
(highlightNode?.pathFromEntry
287-
.map((idWithLevel, k) => {
288-
const [_level, id] = parseNodeIdWidthLevel(idWithLevel);
289-
if (k > 0 && _level === level) {
290-
return {
291-
source: highlightNode.pathFromEntry[k - 1],
292-
target: idWithLevel,
293-
};
294-
}
295-
return null;
296-
})
297-
.filter((a) => a) as TD3Link[]) || [];
298-
const path2NeighborLinks =
299-
highlightNode && level === highlightNode.level
300-
? highlightNode.links.map(
301-
(neighborId) =>
302-
({
303-
source: highlightNode.idWithLevel,
304-
target: getNodeIdWithLevel(neighborId, highlightNode.level),
305-
} as TD3Link)
306-
)
307-
: [];
308-
309-
renderLayer.call(this, this.overviewLayerPosLevels[i]);
310-
renderLinks.call(this, baseLinks, pathFromEntryLinks, path2NeighborLinks);
311-
}
312-
313-
renderNodes.call(this);
314-
315-
if (!!this.hoveredNode) {
316-
const nodePos = this.hoveredNode.overviewPos;
317-
const origin = vecMultiply(
318-
vecAdd(
319-
this.overviewLayerPosLevels[0][0],
320-
this.overviewLayerPosLevels[0][2]
321-
),
322-
0.5
323-
);
324-
const reverse = this.hoveredNode.overviewPos[0] < origin[0];
325-
const tooltipPos = renderTipLine.call(this, nodePos, reverse);
326-
this.updateHoveredPanel(
327-
vecMultiply(tooltipPos, 1 / this.viewParams.canvasScale) as TCoord,
328-
reverse
329-
);
330-
} else this.updateHoveredPanel(null);
331-
}
332130
}

0 commit comments

Comments
 (0)