import d3 from 'd3'; import style from 'PVWStyle/InfoVizNative/FieldSelector.mcss';
import CompositeClosureHelper from 'paraviewweb/src/Common/Core/CompositeClosureHelper'; import template from 'paraviewweb/src/InfoViz/Native/FieldSelector/template.html';
function fieldSelector(publicAPI, model) { const hideField = { minMax: false, hist: false, minMaxWidth: 0, histWidth: 0, };
if (!model.histograms) { model.histograms = {}; }
publicAPI.resize = () => { publicAPI.render(); };
publicAPI.setContainer = (el) => { if (model.container) { while (model.container.firstChild) { model.container.removeChild(model.container.firstChild); } model.container = null; }
model.container = el;
if (el) { d3.select(model.container).html(template); d3.select(model.container) .select('.fieldSelector') .classed(style.fieldSelector, true);
model.fieldShowHistogram = model.fieldShowHistogram && model.provider.isA('Histogram1DProvider'); if (model.fieldShowHistogram) { const header = d3.select(model.container).select('thead').select('tr'); header.append('th').text('Min').classed(style.jsHistMin, true); header.append('th').text('Histogram').classed(style.jsSparkline, true); header.append('th').text('Max').classed(style.jsHistMax, true); } publicAPI.render(); } };
publicAPI.render = () => { if (!model.container) { return; }
const legendSize = 15;
d3.select(model.container).select('thead').classed(style.thead, true); d3.select(model.container).select('tbody').classed(style.tbody, true); d3.select(model.container) .select('th.field-selector-mode') .on('click', (d) => { model.displayUnselected = !model.displayUnselected; publicAPI.render(); }) .select('i') .classed( !model.displayUnselected ? style.allFieldsIcon : style.selectedFieldsIcon, false ) .classed( model.displayUnselected ? style.allFieldsIcon : style.selectedFieldsIcon, true );
const data = model.displayUnselected ? model.provider.getFieldNames() : model.provider.getActiveFieldNames(); const totalNum = model.displayUnselected ? data.length : model.provider.getFieldNames().length;
d3.select(model.container) .select('th.field-selector-label') .style('text-align', 'left') .text( model.displayUnselected ? `Only Selected (${data.length} total)` : `Only Selected (${data.length} / ${totalNum} total)` ) .on('click', (d) => { model.displayUnselected = !model.displayUnselected; publicAPI.render(); });
const hideMore = model.container.scrollWidth > model.container.clientWidth; if (hideMore) { if (!hideField.minMax) { hideField.minMax = true; hideField.minMaxWidth = model.container.scrollWidth; setTimeout(publicAPI.resize, 0); } else if (!hideField.hist) { hideField.hist = true; hideField.histWidth = model.container.scrollWidth; } } else if (hideField.minMax) { if (hideField.hist) { if (model.container.scrollWidth - hideField.histWidth > 0) { hideField.hist = false; hideField.histWidth = 0; setTimeout(publicAPI.resize, 0); } } else if (hideField.minMax) { if (model.container.scrollWidth - hideField.minMaxWidth > 0) { hideField.minMax = false; hideField.minMaxWidth = 0; } } } const header = d3.select(model.container).select('thead').select('tr'); header .selectAll(`.${style.jsHistMin}`) .style('display', hideField.minMax ? 'none' : null); header .selectAll(`.${style.jsSparkline}`) .style('display', hideField.hist ? 'none' : null); header .selectAll(`.${style.jsHistMax}`) .style('display', hideField.minMax ? 'none' : null);
const variablesContainer = d3 .select(model.container) .select('tbody.fields') .selectAll('tr') .data(data);
variablesContainer.enter().append('tr'); variablesContainer.exit().remove();
function renderField(fieldName, index) { const field = model.provider.getField(fieldName); const fieldContainer = d3.select(this); let legendCell = fieldContainer.select(`.${style.jsLegend}`); let fieldCell = fieldContainer.select(`.${style.jsFieldName}`);
fieldContainer .classed(!field.active ? style.selectedRow : style.unselectedRow, false) .classed(field.active ? style.selectedRow : style.unselectedRow, true) .on('click', (name) => { model.provider.toggleFieldSelection(name); });
if (legendCell.empty()) { legendCell = fieldContainer.append('td').classed(style.legend, true);
fieldCell = fieldContainer.append('td').classed(style.fieldName, true); }
if (model.provider.isA('LegendProvider')) { const { color, shape } = model.provider.getLegend(fieldName); legendCell.html(`<svg class='${style.legendSvg}' width='${legendSize}' height='${legendSize}' viewBox='${shape.viewBox}' fill='${color}' stroke='black'><use xlink:href='#${shape.id}'/></svg>`); } else { legendCell .html('<i></i>') .select('i') .classed( !field.active ? style.selectedRow : style.unselectedRow, false ) .classed( field.active ? style.selectedRow : style.unselectedRow, true ); }
fieldCell.text(fieldName);
if (model.fieldShowHistogram) { let minCell = fieldContainer.select(`.${style.jsHistMin}`); let histCell = fieldContainer.select(`.${style.jsSparkline}`); let maxCell = fieldContainer.select(`.${style.jsHistMax}`);
if (histCell.empty()) { minCell = fieldContainer.append('td').classed(style.jsHistMin, true); histCell = fieldContainer.append('td').classed(style.sparkline, true); maxCell = fieldContainer.append('td').classed(style.jsHistMax, true); histCell .append('svg') .classed(style.sparklineSvg, true) .attr('width', model.fieldHistWidth) .attr('height', model.fieldHistHeight); }
const hobj = model.histograms ? model.histograms[fieldName] : null; if (hobj) { histCell.style('display', hideField.hist ? 'none' : null);
if (!hideField.hist) { const cmax = 1.0 * d3.max(hobj.counts); const hsize = hobj.counts.length; const hdata = histCell .select('svg') .selectAll(`.${style.jsHistRect}`) .data(hobj.counts);
hdata.enter().append('rect'); hdata .attr('class', (d, i) => i % 2 === 0 ? style.histRectEven : style.histRectOdd ) .attr('pname', fieldName) .attr('y', (d) => model.fieldHistHeight * (1.0 - d / cmax)) .attr('x', (d, i) => (model.fieldHistWidth / hsize) * i) .attr('height', (d) => model.fieldHistHeight * (d / cmax)) .attr('width', model.fieldHistWidth / hsize);
hdata.exit().remove();
if (model.provider.isA('HistogramBinHoverProvider')) { histCell .select('svg') .on('mousemove', function inner(d, i) { const mCoords = d3.mouse(this); const binNum = Math.floor( (mCoords[0] / model.fieldHistWidth) * hsize ); const state = {}; state[fieldName] = [binNum]; model.provider.setHoverState({ state }); }) .on('mouseout', (d, i) => { const state = {}; state[fieldName] = [-1]; model.provider.setHoverState({ state }); }); } }
const formatter = d3.format('.3s'); minCell .text(formatter(hobj.min)) .style('display', hideField.minMax ? 'none' : null); maxCell .text(formatter(hobj.max)) .style('display', hideField.minMax ? 'none' : null); } } }
variablesContainer.each(renderField); };
function handleHoverUpdate(data) { const svg = d3.select(model.container); Object.keys(data.state).forEach((pName) => { const binList = data.state[pName]; svg .selectAll(`rect[pname='${pName}']`) .classed(style.histoHilite, (d, i) => binList.indexOf(-1) === -1) .classed(style.binHilite, (d, i) => binList.indexOf(i) >= 0); }); }
publicAPI.setContainer(model.container);
model.subscriptions.push({ unsubscribe: publicAPI.setContainer });
model.subscriptions.push( model.provider.onFieldChange(() => { publicAPI.render(); model.histogram1DDataSubscription.update(model.provider.getFieldNames(), { numberOfBins: model.numberOfBins, partial: true, }); }) );
if (model.fieldShowHistogram) { if (model.provider.isA('Histogram1DProvider')) { model.histogram1DDataSubscription = model.provider.subscribeToHistogram1D( (allHistogram1d) => { Object.keys(allHistogram1d).forEach((paramName) => { model.histograms[paramName] = allHistogram1d[paramName]; }); publicAPI.render(); }, model.provider.getFieldNames(), { numberOfBins: model.numberOfBins, partial: true, } );
model.subscriptions.push(model.histogram1DDataSubscription); } }
if (model.provider.isA('HistogramBinHoverProvider')) { model.subscriptions.push( model.provider.onHoverBinChange(handleHoverUpdate) ); } }
const DEFAULT_VALUES = { container: null, provider: null, displayUnselected: true, fieldShowHistogram: true, fieldHistWidth: 120, fieldHistHeight: 15, numberOfBins: 32, };
export function extend(publicAPI, model, initialValues = {}) { Object.assign(model, DEFAULT_VALUES, initialValues);
CompositeClosureHelper.destroy(publicAPI, model); CompositeClosureHelper.isA(publicAPI, model, 'VizComponent'); CompositeClosureHelper.get(publicAPI, model, [ 'provider', 'container', 'fieldShowHistogram', 'numberOfBins', ]); CompositeClosureHelper.set(publicAPI, model, [ 'fieldShowHistogram', 'numberOfBins', ]);
fieldSelector(publicAPI, model); }
export const newInstance = CompositeClosureHelper.newInstance(extend);
export default { newInstance, extend };
|