SelectionProvider

Source

counts.js
// ----------------------------------------------------------------------------
// count
// ----------------------------------------------------------------------------
//
// ===> SET
//
// const payload = {
// type: 'count',
// data: {
// annotationInfo: {
// annotationGeneration: 1,
// selectionGeneration: 1,
// },
// count: 20,
// role: {
// selected: true,
// score: 0,
// },
// },
// }
//
// ===> GET
//
// const query = {
// type: 'count',
// }
//
// const response = [
// {
// },
// ];
//
// ===> NOTIFICATION
//
// request = {
// type: 'count',
// variables: [],
// metadata: {},
// }
//
// const notification = {
// };
//
// ----------------------------------------------------------------------------

function keep(id) {
return (item) => item.annotationInfo.annotationGeneration === id;
}

function sortByScore(a, b) {
return a.role.score - b.role.score;
}

export function set(model, payload) {
const { annotationGeneration } = payload.data.annotationInfo;
model.count = (model.count || [])
.filter(keep(annotationGeneration))
.concat(payload.data);
model.count.sort(sortByScore);
model.count = model.count.filter(
(item, idx, array) => !idx || array[idx - 1].role.score !== item.role.score
);
}

// ----------------------------------------------------------------------------

function get(model, query) {
return model.count;
}

// ----------------------------------------------------------------------------

function getNotificationData(model, request) {
return get(model);
}

// ----------------------------------------------------------------------------

export default {
set,
get,
getNotificationData,
};
dataHelper.js
import histogram2d from 'paraviewweb/src/InfoViz/Core/SelectionProvider/histogram2d';
import counts from 'paraviewweb/src/InfoViz/Core/SelectionProvider/counts';

const dataMapping = {
histogram2d,
counts,
};

// ----------------------------------------------------------------------------

function getHandler(type) {
const handler = dataMapping[type];
if (handler) {
return handler;
}

throw new Error(`No set handler for ${type}`);
}

function set(model, data) {
return getHandler(data.type).set(model, data);
}

function get(model, data) {
return getHandler(data.type).get(model, data);
}

function getNotificationData(model, request) {
return getHandler(request.type).getNotificationData(model, request);
}

export default {
set,
get,
getNotificationData,
};
histogram2d.js
// ----------------------------------------------------------------------------
// Histogram2d
// ----------------------------------------------------------------------------
//
// ===> SET
//
// const payload = {
// type: 'histogram2d',
// filter: '7c1ce0b1-4ecd-4415-80d3-00364c1f7f8b',
// data: {
// x: 'temperature',
// y: 'pressure',
// bins: [...],
// annotationInfo: {
// annotationGeneration: 2,
// selectionGeneration: 5,
// },
// role: {
// score: 0,
// selected: true,
// },
// },
// }
//
// ===> GET
//
// const query = {
// type: 'histogram2d',
// axes: ['temperature', 'pressure'],
// score: [0, 2],
// }
//
// const response = [
// {
// x: 'temperature',
// y: 'pressure',
// bins: [],
// annotationInfo: {
// annotationGeneration: 2,
// selectionGeneration: 5,
// },
// role: {
// score: 0,
// selected: true,
// },
// maxCount: 3534,
// }, {
// x: 'temperature',
// y: 'pressure',
// bins: [],
// annotationInfo: {
// annotationGeneration: 2,
// selectionGeneration: 5,
// },
// role: {
// score: 0,
// selected: true,
// },
// maxCount: 3534,
// },
// ];
//
// ===> NOTIFICATION
//
// request = {
// type: 'histogram2d',
// variables: [
// ['temperature', 'pressure'],
// ['pressure', 'velocity'],
// ['velocity', 'abcd'],
// ],
// metadata: {
// partitionScore: [0, 2],
// },
// }
//
// const notification = {
// temperature: {
// pressure: [
// {
// x: 'temperature',
// y: 'pressure',
// bins: [],
// annotationInfo: {
// annotationGeneration: 2,
// selectionGeneration: 5,
// },
// role: {
// score: 0,
// selected: true,
// },
// maxCount: 3534,
// }, {
// x: 'temperature',
// y: 'pressure',
// bins: [],
// annotationInfo: {
// annotationGeneration: 2,
// selectionGeneration: 5,
// },
// role: {
// score: 2,
// selected: true,
// },
// maxCount: 3534,
// },
// ],
// },
// pressure: {
// velocity: [
// {
// x: 'pressure',
// y: 'velocity',
// bins: [],
// annotationInfo: {
// annotationGeneration: 2,
// selectionGeneration: 5,
// },
// role: {
// score: 0,
// selected: true,
// },
// maxCount: 3534,
// }, {
// x: 'pressure',
// y: 'velocity',
// bins: [],
// annotationInfo: {
// annotationGeneration: 2,
// selectionGeneration: 5,
// },
// role: {
// score: 2,
// selected: true,
// },
// maxCount: 3534,
// },
// ],
// },
// velocity: {
// abcd: [
// {
// x: 'velocity',
// y: 'abcd',
// bins: [],
// annotationInfo: {
// annotationGeneration: 2,
// selectionGeneration: 5,
// },
// role: {
// score: 0,
// selected: true,
// },
// maxCount: 3534,
// }, {
// x: 'velocity',
// y: 'abcd',
// bins: [],
// annotationInfo: {
// annotationGeneration: 2,
// selectionGeneration: 5,
// },
// role: {
// score: 2,
// selected: true,
// },
// maxCount: 3534,
// },
// ],
// },
// };
//
// ----------------------------------------------------------------------------

// function flipHistogram(histo2d) {
// const newHisto2d = {
// bins: histo2d.bins.map(bin => {
// const { x, y, count } = bin;
// return {
// x: y,
// y: x,
// count,
// };
// }),
// x: histo2d.y,
// y: histo2d.x };

// return newHisto2d;
// }

// ----------------------------------------------------------------------------

export function set(model, payload) {
if (!model.histogram2d) {
model.histogram2d = {};
}
const { x, y, annotationInfo, role } = payload.data;
if (!model.histogram2d[x.name]) {
model.histogram2d[x.name] = {};
}
if (!model.histogram2d[x.name][y.name]) {
model.histogram2d[x.name][y.name] = [];
}

model.histogram2d[x.name][y.name] = [].concat(
payload.data,
model.histogram2d[x.name][y.name].filter(
(hist) =>
hist.annotationInfo.annotationGeneration ===
annotationInfo.annotationGeneration && hist.role.score !== role.score
)
);

// Attach max count
let count = 0;
payload.data.bins.forEach((item) => {
count = count < item.count ? item.count : count;
});
payload.data.maxCount = count;

// Create flipped histogram?
// FIXME
}

// ----------------------------------------------------------------------------

function get(model, query) {
if (
model.histogram2d &&
model.histogram2d[query.axes[0]] &&
model.histogram2d[query.axes[0]][query.axes[1]]
) {
if (query.score) {
return model.histogram2d[query.axes[0]][query.axes[1]].filter(
(hist) => query.score.indexOf(hist.role.score) !== -1
);
}
return model.histogram2d[query.axes[0]][query.axes[1]];
}
return null;
}

// ----------------------------------------------------------------------------

function getNotificationData(model, request) {
const result = {};
let missingData = false;
const generationNumbers = [];

request.variables.forEach((axes) => {
const histograms = get(model, { axes });
if (histograms && histograms.length) {
if (!result[axes[0]]) {
result[axes[0]] = {};
}
result[axes[0]][axes[1]] = histograms;
histograms.forEach((hist) =>
generationNumbers.push(hist.annotationInfo.annotationGeneration)
);
} else {
missingData = true;
}
});

// Prevent generation mix in result
generationNumbers.sort();
const generation = generationNumbers.shift();
if (generationNumbers.length && generation !== generationNumbers.pop()) {
return null;
}

result['##annotationGeneration##'] = generation;

return missingData ? null : result;
}

// ----------------------------------------------------------------------------

export default {
set,
get,
getNotificationData,
};
index.js
import CompositeClosureHelper from 'paraviewweb/src/Common/Core/CompositeClosureHelper';
import dataHelper from 'paraviewweb/src/InfoViz/Core/SelectionProvider/dataHelper';

// ----------------------------------------------------------------------------
// Selection Provider
// ----------------------------------------------------------------------------

function selectionProvider(publicAPI, model) {
const dataSubscriptions = [];

if (!model.selectionData) {
model.selectionData = {};
}
if (!model.selectionMetaData) {
model.selectionMetaData = {};
}

function off() {
let count = dataSubscriptions.length;
while (count) {
count -= 1;
dataSubscriptions[count] = null;
}
}

function flushDataToListener(dataListener, dataChanged) {
try {
if (dataListener) {
const event = dataHelper.getNotificationData(
model.selectionData,
dataListener.request
);
if (event) {
if (dataChanged && dataChanged.type === dataListener.request.type) {
dataListener.onDataReady(event);
} else if (!dataChanged) {
dataListener.onDataReady(event);
}
}
}
} catch (err) {
console.log('flushDataToListener error caught:', err);
}
}

// Method use to store received data
publicAPI.setSelectionData = (data) => {
dataHelper.set(model.selectionData, data);

// Process all subscription to see if we can trigger a notification
dataSubscriptions.forEach((listener) =>
flushDataToListener(listener, data)
);
};

// Method use to access cached data. Will return undefined if not available
publicAPI.getSelectionData = (query) =>
dataHelper.get(model.selectionData, query);

// Use to extend data subscription
publicAPI.updateSelectionMetadata = (addon) => {
model.selectionMetaData[addon.type] = Object.assign(
{},
model.selectionMetaData[addon.type],
addon.metadata
);
};

// Get metadata for a given data type
publicAPI.getSelectionMetadata = (type) => model.selectionMetaData[type];

// --------------------------------

publicAPI.setSelection = (selection) => {
model.selection = selection;
publicAPI.fireSelectionChange(selection);
};

// --------------------------------

// annotation = {
// selection: {...},
// score: [0],
// weight: 1,
// rationale: 'why not...',
// }

publicAPI.setAnnotation = (annotation) => {
model.annotation = annotation;
if (annotation.selection) {
publicAPI.setSelection(annotation.selection);
} else {
annotation.selection = model.selection;
}
model.shouldCreateNewAnnotation = false;
publicAPI.fireAnnotationChange(annotation);
};

// --------------------------------

publicAPI.shouldCreateNewAnnotation = () => model.shouldCreateNewAnnotation;
publicAPI.setCreateNewAnnotationFlag = (shouldCreate) => {
model.shouldCreateNewAnnotation = shouldCreate;
return shouldCreate;
};

// --------------------------------
// When a new selection is made, data dependent on that selection will be pushed
// to subscribers.
// A subscriber should save the return value and call update() when they need to
// change the variables or meta data which is pushed to them.
publicAPI.subscribeToDataSelection = (
type,
onDataReady,
variables = [],
metadata = {}
) => {
const id = dataSubscriptions.length;
const request = { id, type, variables, metadata };
const dataListener = { onDataReady, request };
dataSubscriptions.push(dataListener);
publicAPI.fireDataSelectionSubscriptionChange(request);
flushDataToListener(dataListener, null);
return {
unsubscribe() {
request.action = 'unsubscribe';
publicAPI.fireDataSelectionSubscriptionChange(request);
dataSubscriptions[id] = null;
},
update(vars, meta) {
request.variables = [].concat(vars);
request.metadata = Object.assign({}, request.metadata, meta);
publicAPI.fireDataSelectionSubscriptionChange(request);
flushDataToListener(dataListener, null);
},
};
};

publicAPI.destroy = CompositeClosureHelper.chain(off, publicAPI.destroy);
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

const DEFAULT_VALUES = {
// selection: null,
// selectionData: null,
// selectionMetaData: null,
shouldCreateNewAnnotation: false,
};

// ----------------------------------------------------------------------------

export function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);

CompositeClosureHelper.destroy(publicAPI, model);
CompositeClosureHelper.isA(publicAPI, model, 'SelectionProvider');
CompositeClosureHelper.get(publicAPI, model, ['selection', 'annotation']);
CompositeClosureHelper.event(publicAPI, model, 'selectionChange');
CompositeClosureHelper.event(publicAPI, model, 'annotationChange');
CompositeClosureHelper.event(
publicAPI,
model,
'dataSelectionSubscriptionChange'
);

selectionProvider(publicAPI, model);
}

// ----------------------------------------------------------------------------

export const newInstance = CompositeClosureHelper.newInstance(extend);

// ----------------------------------------------------------------------------

export default { newInstance, extend };