Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 53 additions & 23 deletions src/components/VisualizeChart/renderBy.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,50 +26,48 @@ const PALLETE = [
'#651067',
];

// const SELECTED_BORDER_COLOR = '#881177';

function colorByPayload(payloadValue, colored) {
if (payloadValue === undefined || payloadValue === null) {
return BACKGROUND_COLOR;
}

if (colored[payloadValue]) {
return colored[payloadValue];
}

const nextColorIndex = Object.keys(colored).length % PALLETE.length;

colored[payloadValue] = PALLETE[nextColorIndex];

return PALLETE[nextColorIndex];
}

// This function generates an array of colors for each point in the chart.
// There are following options available for colorBy:
//
// - None: all points will have the same color
// - typeof = "string": color points based on the source field
// - {"payload": "field_name"}: color points based on the payload field
// - {"discover_score": { ... } }: color points based on the discover score
// - {"query": { ... }}: color points based on the query score
// --------------------------------------------------
// COLOR GENERATION
// --------------------------------------------------

export function generateColorBy(points, colorBy = null) {
// Points example:
// [
// { id: 0, payload: { field_name: 1 }, score: 0.5, vector: [0.1, 0.2, ....] },
// { id: 1, payload: { field_name: 2 }, score: 0.6, vector: [0.3, 0.4, ....] },
// ...
// ]
if (!points || points.length === 0) {
return [];
}

// Default: single color
if (!colorBy) {
return Array.from({ length: points.length }, () => BACKGROUND_COLOR); // Default color
return Array.from({ length: points.length }, () => BACKGROUND_COLOR);
}

// If `colorBy` is a string, interpret as a field name
// If `colorBy` is a string → payload field
if (typeof colorBy === 'string') {
colorBy = { payload: colorBy };
}

function getNestedValue(obj, path) {
if (!obj) return undefined;
return path.split('.').reduce((acc, part) => acc && acc[part], obj);
}

// -------------------------------
// Payload-based coloring
// -------------------------------
if (colorBy.payload) {
const valuesToColor = {};

Expand All @@ -79,20 +77,52 @@ export function generateColorBy(points, colorBy = null) {
});
}

// -------------------------------
// Score-based coloring
// (query / discover / HNSW-safe)
// -------------------------------
if (colorBy.query) {
const scores = points.map((point) => point.score);
const scores = points
.map((point) => point.score)
.filter((score) => typeof score === 'number');

if (scores.length === 0) {
return Array.from({ length: points.length }, () => BACKGROUND_COLOR);
}

const minScore = Math.min(...scores);
const maxScore = Math.max(...scores);

// Avoid division by zero
if (minScore === maxScore) {
return Array.from({ length: points.length }, () => SCORE_GRADIENT_COLORS[1]);
}

const colorScale = chroma.scale(SCORE_GRADIENT_COLORS);
return scores.map((score) => {
const normalizedScore = (score - minScore) / (maxScore - minScore);

return points.map((point) => {
if (typeof point.score !== 'number') {
return BACKGROUND_COLOR;
}

const normalizedScore = (point.score - minScore) / (maxScore - minScore);
return colorScale(normalizedScore).hex();
});
}

// Fallback
return Array.from({ length: points.length }, () => BACKGROUND_COLOR);
}

// --------------------------------------------------
// SIZE GENERATION
// --------------------------------------------------

export function generateSizeBy(points) {
// ToDo: Intoroduce size differentiation later
if (!points || points.length === 0) {
return [];
}

// HNSW-safe default
return points.map(() => 3);
}
35 changes: 29 additions & 6 deletions src/components/VisualizeChart/requestData.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
/* eslint-disable camelcase */
export function requestData(qdrantClient, collectionName, { limit, filter = null, using = null, color_by = null }) {
// Based on the input parameters, we need to decide what kind of request we need to send
// By default we should do scroll request
// But if we have color_by field which uses query, it should be used instead

export function requestData(
qdrantClient,
collectionName,
{ limit, filter = null, using = null, color_by = null, method = null }
) {
// --------------------------------------------------
// HNSW-based visualization request
// --------------------------------------------------
if (method === 'hnsw') {
/**
* HNSW visualization does NOT require vectors.
* Backend is expected to return precomputed 2D points.
*/
return qdrantClient.http.post(
`/collections/${collectionName}/visualize`,
{
method: 'hnsw',
limit,
filter,
}
);
}

// --------------------------------------------------
// Query-based coloring (query / discover)
// --------------------------------------------------
if (color_by?.query) {
const query = {
query: color_by.query,
Expand All @@ -17,8 +39,9 @@ export function requestData(qdrantClient, collectionName, { limit, filter = null
return qdrantClient.query(collectionName, query);
}

// It it's not a query, we should do a scroll request

// --------------------------------------------------
// Default: scroll request
// --------------------------------------------------
const scrollQuery = {
limit: limit,
filter: filter,
Expand Down