import d3 from 'd3'; import deepEquals from 'mout/src/lang/deepEquals';
import style from 'PVWStyle/InfoVizNative/HistogramSelector.mcss';
import SelectionBuilder from 'paraviewweb/src/Common/Misc/SelectionBuilder'; import AnnotationBuilder from 'paraviewweb/src/Common/Misc/AnnotationBuilder';
export default function init(inPublicAPI, inModel) { const publicAPI = inPublicAPI; const model = inModel; let displayOnlyScored = false; let scorePopupDiv = null; let dividerPopupDiv = null; let dividerValuePopupDiv = null;
publicAPI.setScores = (scores, defaultScore) => { model.scores = scores; model.defaultScore = defaultScore; if (model.scores) { model.scores.forEach((score, i) => { const lightness = d3.hsl(score.color).l; const blend = lightness >= 0.45 ? 0.4 : 0.2; const interp = d3.interpolateRgb('#fff', score.color); score.bgColor = interp(blend); }); } };
function enabled() { return model.scores !== undefined; }
if (model.provider.isA('ScoresProvider')) { publicAPI.setScores( model.provider.getScores(), model.provider.getDefaultScore() ); }
function defaultFieldData() { return { annotation: null, }; }
function createDefaultDivider(val, uncert) { return { value: val, uncertainty: uncert, }; }
function getHistRange(def) { let minRange = def.range[0]; let maxRange = def.range[1]; if (def.hobj) { minRange = def.hobj.min; maxRange = def.hobj.max; } if (minRange === maxRange) maxRange += 1; return [minRange, maxRange]; } function getRegionBounds(def) { const [minRange, maxRange] = getHistRange(def); return [minRange].concat( def.dividers.map((div) => div.value), maxRange ); }
function getUncertScale(def) {
return 1.0; }
function dividersToPartition(def, scores) { if (!def.regions || !def.dividers || !scores) return null; if (def.regions.length !== def.dividers.length + 1) return null; const uncertScale = getUncertScale(def);
const partitionSelection = SelectionBuilder.partition( def.name, def.dividers ); partitionSelection.partition.dividers.forEach((div, index) => { div.uncertainty *= uncertScale; });
let partitionAnnotation = null; if (def.annotation && !model.provider.shouldCreateNewAnnotation()) { const saveGen = partitionSelection.generation; partitionSelection.generation = def.annotation.selection.generation; const changeSet = { score: def.regions }; if (!deepEquals(partitionSelection, def.annotation.selection)) { partitionSelection.generation = saveGen; changeSet.selection = partitionSelection; } partitionAnnotation = AnnotationBuilder.update(def.annotation, { selection: partitionSelection, score: def.regions, }); } else { partitionAnnotation = AnnotationBuilder.annotation( partitionSelection, def.regions, 1, '' ); } AnnotationBuilder.updateReadOnlyFlag( partitionAnnotation, model.readOnlyFields ); return partitionAnnotation; }
function partitionToDividers(scoreData, def, scores) { const uncertScale = getUncertScale(def); const regions = scoreData.score; const dividers = JSON.parse( JSON.stringify(scoreData.selection.partition.dividers) ); dividers.forEach((div, index) => { div.uncertainty *= 1 / uncertScale; });
if ( regions.length > 0 && !(regions.length === 1 && regions[0] === model.defaultScore) ) { def.regions = [].concat(regions); def.dividers = dividers; } }
function sendScores(def, passive = false) { const scoreData = dividersToPartition(def, model.scores); if (scoreData === null) { console.error('Cannot translate scores to send to provider'); return; } if (model.provider.isA('SelectionProvider')) { if (!scoreData.name) { AnnotationBuilder.setDefaultName(scoreData); if (model.provider.isA('AnnotationStoreProvider')) { scoreData.name = model.provider.getNextStoredAnnotationName( scoreData.name ); } } if (!passive) { model.provider.setAnnotation(scoreData); } else if ( model.provider.isA('AnnotationStoreProvider') && model.provider.getStoredAnnotation(scoreData.id) ) { model.provider.updateStoredAnnotations({ [scoreData.id]: scoreData, }); } } }
function showScore(def) { return ( def.editScore || (typeof def.regions !== 'undefined' && (def.regions.length > 1 || def.regions[0] !== model.defaultScore)) ); }
function setEditScore(def, newEditScore) { def.editScore = newEditScore; if (def.editScore && showScore(def) && def.annotation) { sendScores(def); } publicAPI.render(def.name); if (newEditScore) { Object.keys(model.fieldData).forEach((key) => { const d = model.fieldData[key]; if (d !== def) { if (d.editScore) { d.editScore = false; publicAPI.render(d.name); } } }); } }
publicAPI.setDefaultScorePartition = (fieldName) => { const def = model.fieldData[fieldName]; if (!def) return; if ( !def.lockAnnot || !( def.regions && def.regions.length === 2 && def.regions[0] === 0 && def.regions[1] === 2 ) ) { const [minRange, maxRange] = getHistRange(def); def.dividers = [createDefaultDivider(0.5 * (minRange + maxRange), 0)]; def.regions = [0, 2]; def.annotation = null; def.lockAnnot = true; sendScores(def); } setEditScore(def, true); };
publicAPI.getScoreThreshold = (fieldName) => { const def = model.fieldData[fieldName]; if (!def.lockAnnot) console.log('Wrong mode for score threshold, arbitrary results.'); return def.dividers[0].value; };
const scoredHeaderClick = (d) => { displayOnlyScored = !displayOnlyScored; publicAPI.render(); };
function getDisplayOnlyScored() { return displayOnlyScored; }
function numScoreIcons(def) { if (!enabled()) return 0; let count = 0; if ( model.provider.getStoredAnnotation && !publicAPI.isFieldActionDisabled(def.name, 'save') ) { count += 1; } if (!publicAPI.isFieldActionDisabled(def.name, 'score')) { count += 1; } return count; }
function annotationSameAsStored(annotation) { const storedAnnot = model.provider.getStoredAnnotation(annotation.id); if (!storedAnnot) return false; if (annotation.generation === storedAnnot.generation) return true; const savedGen = annotation.generation; annotation.generation = storedAnnot.generation; const ret = deepEquals(annotation, storedAnnot); annotation.generation = savedGen; return ret; }
function createScoreIcons(iconCell) { if (!enabled()) return; if (model.provider.getStoredAnnotation) { iconCell .append('i') .classed(style.noSaveIcon, true) .on('click', (d) => { if (model.provider.getStoredAnnotation) { const annotation = d.annotation; const isSame = annotationSameAsStored(annotation); if (!isSame) { model.provider.setStoredAnnotation(annotation.id, annotation); } else { model.provider.setAnnotation(annotation); } publicAPI.render(d.name); if (d3.event) d3.event.stopPropagation(); } }); }
iconCell .append('i') .classed(style.scoreStartIcon, true) .on('click', (d) => { setEditScore(d, !d.editScore); if (d3.event) d3.event.stopPropagation(); }); }
function updateScoreIcons(iconCell, def) { if (!enabled()) return;
if (model.provider.getStoredAnnotation) { if (def.annotation) { if (model.provider.getStoredAnnotation(def.annotation.id)) { const isSame = annotationSameAsStored(def.annotation); if (isSame) { const isActive = def.annotation === model.provider.getAnnotation(); iconCell .select(`.${style.jsSaveIcon}`) .attr( 'class', isActive ? style.unchangedActiveSaveIcon : style.unchangedSaveIcon ); } else { iconCell .select(`.${style.jsSaveIcon}`) .attr('class', style.modifiedSaveIcon); } } else { iconCell .select(`.${style.jsSaveIcon}`) .attr('class', style.newSaveIcon); } } else { iconCell.select(`.${style.jsSaveIcon}`).attr('class', style.noSaveIcon); } }
iconCell .select(`.${style.jsScoreIcon}`) .attr('class', def.editScore ? style.scoreEndIcon : style.scoreStartIcon);
if (publicAPI.isFieldActionDisabled(def.name, 'save')) { iconCell.select(`.${style.jsSaveIcon}`).attr('class', style.noSaveIcon); }
if (publicAPI.isFieldActionDisabled(def.name, 'score')) { iconCell .select(`.${style.jsScoreIcon}`) .attr('class', style.hideScoreIcon); } }
function createGroups(svgGr) { svgGr.insert('g', ':first-child').classed(style.jsScoreBackground, true); svgGr.append('g').classed(style.score, true); }
function createHeader(header) { if (enabled()) { header .append('span') .on('click', scoredHeaderClick) .append('i') .classed(style.jsShowScoredIcon, true); header .append('span') .classed(style.jsScoredHeader, true) .text('Only Scored') .on('click', scoredHeaderClick); } }
function updateHeader() { if (enabled()) { d3.select(model.container) .select(`.${style.jsShowScoredIcon}`) .classed( getDisplayOnlyScored() ? style.allScoredIcon : style.onlyScoredIcon, false ) .classed( !getDisplayOnlyScored() ? style.allScoredIcon : style.onlyScoredIcon, true ); } }
function createDragDivider(hitIndex, val, def, hobj) { let dragD = null; if (hitIndex >= 0) { dragD = { index: hitIndex, newDivider: createDefaultDivider( undefined, def.dividers[hitIndex].uncertainty ), savedUncert: def.dividers[hitIndex].uncertainty, low: hitIndex === 0 ? hobj.min : def.dividers[hitIndex - 1].value, high: hitIndex === def.dividers.length - 1 ? hobj.max : def.dividers[hitIndex + 1].value, }; } else { dragD = { index: -1, newDivider: createDefaultDivider(val, 0), savedUncert: 0, low: hobj.min, high: hobj.max, }; } return dragD; }
function clampDividerUncertainty(val, def, hitIndex, currentUncertainty) { if (hitIndex < 0) return currentUncertainty; const [minRange, maxRange] = getHistRange(def); let maxUncertainty = 0.5 * (maxRange - minRange); const uncertScale = getUncertScale(def); if (hitIndex > 0) { const low = def.dividers[hitIndex - 1].value + def.dividers[hitIndex - 1].uncertainty * uncertScale; maxUncertainty = Math.min(maxUncertainty, (val - low) / uncertScale); } if (hitIndex < def.dividers.length - 1) { const high = def.dividers[hitIndex + 1].value - def.dividers[hitIndex + 1].uncertainty * uncertScale; maxUncertainty = Math.min((high - val) / uncertScale, maxUncertainty); } maxUncertainty = Math.max(maxUncertainty, 0); return Math.min(maxUncertainty, currentUncertainty); }
function clampDragDividerUncertainty(val, def) { if (def.dragDivider.index < 0) return;
def.dragDivider.newDivider.uncertainty = clampDividerUncertainty( val, def, def.dragDivider.index, def.dragDivider.savedUncert ); def.dividers[def.dragDivider.index].uncertainty = def.dragDivider.newDivider.uncertainty; }
function moveDragDivider(val, def) { if (!def.dragDivider) return; if (def.dragDivider.index >= 0) { if (val < def.dragDivider.low) { def.dragDivider.newDivider.value = val; def.dividers[def.dragDivider.index].value = def.dragDivider.low; clampDragDividerUncertainty(val, def); def.dividers[def.dragDivider.index].uncertainty = 0; } else if (val > def.dragDivider.high) { def.dragDivider.newDivider.value = val; def.dividers[def.dragDivider.index].value = def.dragDivider.high; clampDragDividerUncertainty(val, def); def.dividers[def.dragDivider.index].uncertainty = 0; } else { def.dividers[def.dragDivider.index].value = val; clampDragDividerUncertainty(val, def); def.dragDivider.newDivider.value = undefined; } } else { def.dragDivider.newDivider.value = val; } }
const bisectDividers = d3.bisector((a, b) => a.value - b.value).left;
function dividerPick(overCoords, def, marginPx, minVal) { const val = def.xScale.invert(overCoords[0]); const index = bisectDividers(def.dividers, createDefaultDivider(val)); let hitIndex = -1; if (def.dividers.length > 0) { if (index === 0) { hitIndex = 0; } else if (index === def.dividers.length) { hitIndex = index - 1; } else { hitIndex = def.dividers[index].value - val < val - def.dividers[index - 1].value ? index : index - 1; } const margin = def.xScale.invert(marginPx) - minVal; if ( Math.abs(def.dividers[hitIndex].value - val) > margin || val < def.hobj.min || val > def.hobj.max ) { hitIndex = -1; } } return [val, index, hitIndex]; }
function regionPick(overCoords, def, hobj) { if (def.dividers.length === 0 || def.regions.length <= 1) return 0; const val = def.xScale.invert(overCoords[0]); const hitIndex = bisectDividers(def.dividers, createDefaultDivider(val)); return hitIndex; }
function finishDivider(def, hobj, forceDelete = false) { if (!def.dragDivider) return; const val = def.dragDivider.newDivider.value; if (val !== undefined || forceDelete) { const dragOut = def.xScale.invert(30) - hobj.min; if ( !def.lockAnnot && (forceDelete || val < hobj.min - dragOut || val > hobj.max + dragOut) ) { if (def.dragDivider.index >= 0) { if ( forceDelete || def.dividers[def.dragDivider.index].value === def.dragDivider.high ) { def.regions.splice(def.dragDivider.index + 1, 1); } else { def.regions.splice(def.dragDivider.index, 1); } def.dividers.splice(def.dragDivider.index, 1); } } else { let replaceRegion = true; if (def.dragDivider.index >= 0) { if ( def.dividers[def.dragDivider.index].value === def.dragDivider.low && def.dragDivider.low !== hobj.min ) { def.regions.splice(def.dragDivider.index, 1); } else if ( def.dividers[def.dragDivider.index].value === def.dragDivider.high && def.dragDivider.high !== hobj.max ) { def.regions.splice(def.dragDivider.index + 1, 1); } else { replaceRegion = false; } def.dividers.splice(def.dragDivider.index, 1); } def.dragDivider.newDivider.value = Math.min( hobj.max, Math.max(hobj.min, val) ); const index = bisectDividers(def.dividers, def.dragDivider.newDivider); def.dividers.splice(index, 0, def.dragDivider.newDivider); if (replaceRegion) { def.regions.splice(index, 0, def.regions[index]); } } } else if ( def.dragDivider.index >= 0 && def.dividers[def.dragDivider.index].uncertainty !== def.dragDivider.newDivider.uncertainty ) { def.dividers[def.dragDivider.index].uncertainty = def.dragDivider.newDivider.uncertainty; } def.dividers.forEach((divider, index) => { divider.uncertainty = clampDividerUncertainty( divider.value, def, index, divider.uncertainty ); }); sendScores(def); def.dragDivider = undefined; }
function positionPopup(popupDiv, left, top) { const clientRect = model.listContainer.getBoundingClientRect(); const popupRect = popupDiv.node().getBoundingClientRect(); if (popupRect.width + left > clientRect.width) { popupDiv.style('left', 'auto'); popupDiv.style('right', 0); } else { popupDiv.style('right', null); popupDiv.style('left', `${left}px`); }
if (popupRect.height + top > clientRect.height) { popupDiv.style('top', 'auto'); popupDiv.style('bottom', 0); } else { popupDiv.style('bottom', null); popupDiv.style('top', `${top}px`); } }
function validateDividerVal(n) { return !Number.isNaN(Number(n)) && Number.isFinite(Number(n)); }
function showDividerPopup(dPopupDiv, selectedDef, hobj, coord) { const topMargin = 4; const rowHeight = 28; const formatter = d3.format('.4g'); const uncertDispScale = 1;
dPopupDiv.style('display', null); positionPopup( dPopupDiv, coord[0] - topMargin - 0.5 * rowHeight, coord[1] + model.headerSize - (topMargin + 2 * rowHeight) );
const selDivider = selectedDef.dividers[selectedDef.dragDivider.index]; let savedVal = selDivider.value; selectedDef.dragDivider.savedUncert = selDivider.uncertainty; dPopupDiv.on('mouseleave', () => { if (selectedDef.dragDivider) { moveDragDivider(savedVal, selectedDef); finishDivider(selectedDef, hobj); } dPopupDiv.style('display', 'none'); selectedDef.dragDivider = undefined; publicAPI.render(selectedDef.name); }); const uncertInput = dPopupDiv.select(`.${style.jsDividerUncertaintyInput}`); const valInput = dPopupDiv .select(`.${style.jsDividerValueInput}`) .attr('value', formatter(selDivider.value)) .property('value', formatter(selDivider.value)) .on('input', () => { let val = d3.event.target.value; if (!validateDividerVal(val)) val = savedVal; moveDragDivider(val, selectedDef); uncertInput.property( 'value', formatter( uncertDispScale * selectedDef.dragDivider.newDivider.uncertainty ) ); publicAPI.render(selectedDef.name); }) .on('change', () => { let val = d3.event.target.value; if (!validateDividerVal(val)) val = savedVal; else { val = Math.min(hobj.max, Math.max(hobj.min, val)); d3.event.target.value = val; savedVal = val; } moveDragDivider(val, selectedDef); publicAPI.render(selectedDef.name); }) .on('keyup', () => { if (d3.event.key === 'Escape') { moveDragDivider(savedVal, selectedDef); dPopupDiv.on('mouseleave')(); } else if (d3.event.key === 'Enter' || d3.event.key === 'Return') { if (selectedDef.dragDivider) { savedVal = selectedDef.dragDivider.newDivider.value === undefined ? selectedDef.dividers[selectedDef.dragDivider.index].value : selectedDef.dragDivider.newDivider.value; } dPopupDiv.on('mouseleave')(); } }); valInput.node().select(); valInput.node().focus();
uncertInput .attr('value', formatter(uncertDispScale * selDivider.uncertainty)) .property('value', formatter(uncertDispScale * selDivider.uncertainty)) .on('input', () => { let uncert = d3.event.target.value; if (!validateDividerVal(uncert)) { if (selectedDef.dragDivider) uncert = selectedDef.dragDivider.savedUncert; } else { uncert /= uncertDispScale; } if (selectedDef.dragDivider) { selectedDef.dragDivider.newDivider.uncertainty = uncert; if (selectedDef.dragDivider.newDivider.value === undefined) { selectedDef.dividers[ selectedDef.dragDivider.index ].uncertainty = uncert; } } publicAPI.render(selectedDef.name); }) .on('change', () => { let uncert = d3.event.target.value; if (!validateDividerVal(uncert)) { if (selectedDef.dragDivider) uncert = selectedDef.dragDivider.savedUncert; } else { const [minRange, maxRange] = getHistRange(selectedDef); uncert = Math.min( 0.5 * (maxRange - minRange), Math.max(0, uncert / uncertDispScale) ); d3.event.target.value = formatter(uncertDispScale * uncert); if (selectedDef.dragDivider) selectedDef.dragDivider.savedUncert = uncert; } if (selectedDef.dragDivider) { selectedDef.dragDivider.newDivider.uncertainty = uncert; if (selectedDef.dragDivider.newDivider.value === undefined) { selectedDef.dividers[ selectedDef.dragDivider.index ].uncertainty = uncert; } } publicAPI.render(selectedDef.name); }) .on('keyup', () => { if (d3.event.key === 'Escape') { if (selectedDef.dragDivider) { selectedDef.dragDivider.newDivider.uncertainty = selectedDef.dragDivider.savedUncert; } dPopupDiv.on('mouseleave')(); } else if (d3.event.key === 'Enter' || d3.event.key === 'Return') { if (selectedDef.dragDivider) { selectedDef.dragDivider.savedUncert = selectedDef.dragDivider.newDivider.uncertainty; } dPopupDiv.on('mouseleave')(); } }) .on('blur', () => { if (selectedDef.dragDivider) { const val = selectedDef.dragDivider.newDivider.value === undefined ? selectedDef.dividers[selectedDef.dragDivider.index].value : selectedDef.dragDivider.newDivider.value; clampDragDividerUncertainty(val, selectedDef); d3.event.target.value = formatter( uncertDispScale * selectedDef.dragDivider.newDivider.uncertainty ); } publicAPI.render(selectedDef.name); }); }
function showDividerValuePopup(dPopupDiv, selectedDef, hobj, coord) { const topMargin = 4; const rowHeight = 28; const formatter = d3.format('.4g');
dPopupDiv.style('display', null); positionPopup( dPopupDiv, coord[0] - topMargin - 0.5 * rowHeight, coord[1] + model.headerSize - (topMargin + 0.5 * rowHeight) );
const selDivider = selectedDef.dividers[selectedDef.dragDivider.index]; let savedVal = selDivider.value; selectedDef.dragDivider.savedUncert = selDivider.uncertainty; dPopupDiv.on('mouseleave', () => { if (selectedDef.dragDivider) { moveDragDivider(savedVal, selectedDef); finishDivider(selectedDef, hobj); } dPopupDiv.style('display', 'none'); selectedDef.dragDivider = undefined; publicAPI.render(); }); const valInput = dPopupDiv .select(`.${style.jsDividerValueInput}`) .attr('value', formatter(selDivider.value)) .property('value', formatter(selDivider.value)) .on('input', () => { let val = d3.event.target.value; if (!validateDividerVal(val)) val = savedVal; moveDragDivider(val, selectedDef); publicAPI.render(selectedDef.name); }) .on('change', () => { let val = d3.event.target.value; if (!validateDividerVal(val)) val = savedVal; else { val = Math.min(hobj.max, Math.max(hobj.min, val)); d3.event.target.value = val; savedVal = val; } moveDragDivider(val, selectedDef); publicAPI.render(selectedDef.name); }) .on('keyup', () => { if (d3.event.key === 'Escape') { moveDragDivider(savedVal, selectedDef); dPopupDiv.on('mouseleave')(); } else if (d3.event.key === 'Enter' || d3.event.key === 'Return') { dPopupDiv.on('mouseleave')(); } }); valInput.node().select(); valInput.node().focus(); }
function createDividerPopup() { const dPopupDiv = d3 .select(model.listContainer) .append('div') .classed(style.dividerPopup, true) .style('display', 'none'); const table = dPopupDiv.append('table'); const tr1 = table.append('tr'); tr1.append('td').classed(style.popupCell, true).text('Value:'); tr1 .append('td') .classed(style.popupCell, true) .append('input') .classed(style.jsDividerValueInput, true) .attr('type', 'number') .attr('step', 'any') .style('width', '6em'); const tr2 = table.append('tr'); tr2.append('td').classed(style.popupCell, true).text('Uncertainty:'); tr2 .append('td') .classed(style.popupCell, true) .append('input') .classed(style.jsDividerUncertaintyInput, true) .attr('type', 'number') .attr('step', 'any') .style('width', '6em'); if (!model.showUncertainty) { tr2.style('display', 'none'); } dPopupDiv.append('div').classed(style.scoreDashSpacer, true); dPopupDiv .append('div') .style('text-align', 'center') .append('input') .classed(style.scoreButton, true) .style('align', 'center') .attr('type', 'button') .attr('value', 'Delete Divider') .on('click', () => { finishDivider(model.selectedDef, model.selectedDef.hobj, true); dPopupDiv.style('display', 'none'); publicAPI.render(); }); return dPopupDiv; } function createDividerValuePopup() { const dPopupDiv = d3 .select(model.listContainer) .append('div') .classed(style.dividerValuePopup, true) .style('display', 'none'); const table = dPopupDiv.append('table'); const tr1 = table.append('tr'); tr1.append('td').classed(style.popupCell, true).text('Value:'); tr1 .append('td') .classed(style.popupCell, true) .append('input') .classed(style.jsDividerValueInput, true) .attr('type', 'number') .attr('step', 'any') .style('width', '6em'); return dPopupDiv; }
function showScorePopup(sPopupDiv, coord, selRow) { const topMargin = 4; const rowHeight = 26;
sPopupDiv.style('display', null); positionPopup( sPopupDiv, coord[0] - topMargin - 0.6 * rowHeight, coord[1] + model.headerSize - (topMargin + (0.6 + selRow) * rowHeight) );
sPopupDiv .selectAll(`.${style.jsScoreLabel}`) .style('background-color', (d, i) => (i === selRow ? d.bgColor : '#fff')); }
function createScorePopup() { const sPopupDiv = d3 .select(model.listContainer) .append('div') .classed(style.scorePopup, true) .style('display', 'none') .on('mouseleave', () => { sPopupDiv.style('display', 'none'); model.selectedDef.dragDivider = undefined; }); const scoreChoices = sPopupDiv .selectAll(`.${style.jsScoreChoice}`) .data(model.scores); scoreChoices .enter() .append('label') .classed(style.scoreLabel, true) .text((d) => d.name) .each(function myLabel(data, index) { const label = d3.select(this); label .append('span') .classed(style.scoreSwatch, true) .style('background-color', (d) => d.color); label .append('input') .classed(style.scoreChoice, true) .attr('name', 'score_choice_rb') .attr('type', 'radio') .attr('value', (d) => d.name) .property('checked', (d) => index === model.defaultScore) .on('click', (d) => { const def = model.selectedDef; def.regions[def.hitRegionIndex] = index; def.dragDivider = undefined; sPopupDiv.style('display', 'none'); sendScores(def); publicAPI.render(); }); }); sPopupDiv.append('div').classed(style.scoreDashSpacer, true); sPopupDiv .append('input') .classed(style.scoreButton, true) .attr('type', 'button') .attr('value', 'New Divider') .on('click', () => { finishDivider(model.selectedDef, model.selectedDef.hobj); sPopupDiv.style('display', 'none'); publicAPI.render(); }); return sPopupDiv; }
function createPopups() { if (enabled()) { scorePopupDiv = d3 .select(model.listContainer) .select(`.${style.jsScorePopup}`); if (scorePopupDiv.empty()) { scorePopupDiv = createScorePopup(); } dividerPopupDiv = d3 .select(model.listContainer) .select(`.${style.jsDividerPopup}`); if (dividerPopupDiv.empty()) { dividerPopupDiv = createDividerPopup(); } dividerValuePopupDiv = d3 .select(model.listContainer) .select(`.${style.jsDividerValuePopup}`); if (dividerValuePopupDiv.empty()) { dividerValuePopupDiv = createDividerValuePopup(); } } }
function rescaleDividers(paramName, oldRangeMin, oldRangeMax) { if (model.fieldData[paramName] && model.fieldData[paramName].hobj) { const def = model.fieldData[paramName]; if (showScore(def)) { const hobj = model.fieldData[paramName].hobj; if (hobj.min !== oldRangeMin || hobj.max !== oldRangeMax) { def.dividers.forEach((divider, index) => { if (oldRangeMax === oldRangeMin) { divider.value = ((index + 1) / (def.dividers.length + 1)) * (hobj.max - hobj.min) + hobj.min; } else { divider.value = ((divider.value - oldRangeMin) / (oldRangeMax - oldRangeMin)) * (hobj.max - hobj.min) + hobj.min; } }); sendScores(def, true); } } } }
function editingScore(def) { return def.editScore; }
function filterFieldNames(fieldNames) { if (getDisplayOnlyScored()) { return fieldNames.filter((name) => showScore(model.fieldData[name])); } return fieldNames; }
function prepareItem(def, idx, svgGr, tdsl) { if (!enabled()) return; if (typeof def.dividers === 'undefined') { def.dividers = []; def.regions = [model.defaultScore]; def.editScore = false; def.lockAnnot = false; } const hobj = def.hobj;
const gScore = svgGr.select(`.${style.jsScore}`); let drag = null; if (def.editScore) { const dividerData = typeof def.dragDivider !== 'undefined' && def.dragDivider.newDivider.value !== undefined ? def.dividers.concat(def.dragDivider.newDivider) : def.dividers; const dividers = gScore.selectAll('line').data(dividerData); dividers.enter().append('line'); dividers .attr('x1', (d) => def.xScale(d.value)) .attr('y1', 0) .attr('x2', (d) => def.xScale(d.value)) .attr('y2', () => model.histHeight) .attr('stroke-width', 1) .attr('stroke', 'black'); dividers.exit().remove();
const uncertScale = getUncertScale(def); const uncertRegions = gScore .selectAll(`.${style.jsScoreUncertainty}`) .data(dividerData); uncertRegions .enter() .append('rect') .classed(style.jsScoreUncertainty, true) .attr('rx', 8) .attr('ry', 8); uncertRegions .attr('x', (d) => def.xScale(d.value - d.uncertainty * uncertScale)) .attr('y', 0) .attr('width', (d, i) => def.xScale(hobj.min + 2 * d.uncertainty * uncertScale) ) .attr('height', () => model.histHeight) .attr('fill', '#000') .attr('opacity', (d) => (d.uncertainty > 0 ? '0.2' : '0')); uncertRegions.exit().remove();
let dragDivLabel = gScore.select(`.${style.jsScoreDivLabel}`); if (typeof def.dragDivider !== 'undefined') { if (dragDivLabel.empty()) { dragDivLabel = gScore .append('text') .classed(style.jsScoreDivLabel, true) .attr('text-anchor', 'middle') .attr('stroke', 'none') .attr('background-color', '#fff') .attr('dy', '.71em'); } const formatter = d3.format('.3s'); const divVal = def.dragDivider.newDivider.value !== undefined ? def.dragDivider.newDivider.value : def.dividers[def.dragDivider.index].value; dragDivLabel .text(formatter(divVal)) .attr('x', `${def.xScale(divVal)}`) .attr('y', `${model.histHeight + 2}`); } else if (!dragDivLabel.empty()) { dragDivLabel.remove(); }
drag = d3.behavior .drag() .on('dragstart', () => { const overCoords = publicAPI.getMouseCoords(tdsl); const [val, , hitIndex] = dividerPick( overCoords, def, model.dragMargin, hobj.min ); if ( !def.lockAnnot && (d3.event.sourceEvent.altKey || d3.event.sourceEvent.ctrlKey) ) { def.dragDivider = createDragDivider(-1, val, def, hobj); publicAPI.render(); } else if (hitIndex >= 0) { def.dragDivider = createDragDivider(hitIndex, undefined, def, hobj); publicAPI.render(); } }) .on('drag', () => { const overCoords = publicAPI.getMouseCoords(tdsl); if ( typeof def.dragDivider === 'undefined' || scorePopupDiv.style('display') !== 'none' || dividerPopupDiv.style('display') !== 'none' || dividerValuePopupDiv.style('display') !== 'none' ) return; const val = def.xScale.invert(overCoords[0]); moveDragDivider(val, def); publicAPI.render(def.name); }) .on('dragend', () => { if ( typeof def.dragDivider === 'undefined' || scorePopupDiv.style('display') !== 'none' || dividerPopupDiv.style('display') !== 'none' || dividerValuePopupDiv.style('display') !== 'none' ) return; finishDivider(def, hobj); publicAPI.render(); }); } else { gScore.selectAll('line').remove(); gScore.selectAll(`.${style.jsScoreUncertainty}`).remove(); }
const regionBounds = getRegionBounds(def); const scoreRegions = gScore .selectAll(`.${style.jsScoreRect}`) .data(def.regions); const scoreBgRegions = svgGr .select(`.${style.jsScoreBackground}`) .selectAll('rect') .data(def.regions); const numRegions = def.regions.length; [ { sel: scoreRegions, opacity: 0.2, class: style.scoreRegionFg }, { sel: scoreBgRegions, opacity: 1.0, class: style.scoreRegionBg }, ].forEach((reg) => { reg.sel.enter().append('rect').classed(reg.class, true); const overhang = 6; reg.sel .attr( 'x', (d, i) => def.xScale(regionBounds[i]) - (i === 0 ? overhang : 0) ) .attr('y', def.editScore ? 0 : model.histHeight) .attr( 'width', (d, i) => def.xScale(regionBounds[i + 1]) - def.xScale(regionBounds[i]) + (i === 0 ? overhang : 0) + (i === numRegions - 1 ? overhang : 0) ) .attr( 'height', def.editScore ? model.histHeight + model.histMargin.bottom - 3 : model.histMargin.bottom - 3 ) .attr('fill', (d) => model.scores[d].color) .attr('opacity', showScore(def) ? reg.opacity : '0'); reg.sel.exit().remove(); });
const svgOverlay = svgGr.select(`.${style.jsOverlay}`); svgOverlay .on('click.score', () => { if (d3.event.defaultPrevented || d3.event.altKey || d3.event.ctrlKey) return; const overCoords = publicAPI.getMouseCoords(tdsl); if (overCoords[1] > model.histHeight) { return; } if (def.editScore) { const hitRegionIndex = regionPick(overCoords, def, hobj); def.hitRegionIndex = hitRegionIndex; const [val, , hitIndex] = dividerPick( overCoords, def, model.dragMargin, hobj.min ); const coord = d3.mouse(model.listContainer); model.selectedDef = def; if (hitIndex >= 0) { def.dragDivider = createDragDivider(hitIndex, undefined, def, hobj); if (!def.lockAnnot) showDividerPopup(dividerPopupDiv, model.selectedDef, hobj, coord); else showDividerValuePopup( dividerValuePopupDiv, model.selectedDef, hobj, coord ); } else if (!def.lockAnnot) { if (typeof def.dragDivider === 'undefined') { def.dragDivider = createDragDivider(-1, val, def, hobj); } else { console.log('Internal: unexpected existing divider'); def.dragDivider.newDivider.value = val; }
const selRow = def.regions[def.hitRegionIndex]; showScorePopup(scorePopupDiv, coord, selRow); } } }) .on('mousemove.score', () => { const overCoords = publicAPI.getMouseCoords(tdsl); if (def.editScore) { const [, , hitIndex] = dividerPick( overCoords, def, model.dragMargin, hobj.min ); let cursor = 'pointer'; if (overCoords[1] > model.histHeight) { } else if (def.dragIndex >= 0 || hitIndex >= 0) cursor = 'ew-resize'; else if (d3.event.altKey || d3.event.ctrlKey) cursor = 'crosshair'; svgOverlay.style('cursor', cursor); } else { const pickIt = overCoords[1] > model.histHeight; svgOverlay.style('cursor', pickIt ? 'pointer' : 'default'); } }); if (def.editScore) { svgOverlay.call(drag); } else { svgOverlay.on('.drag', null); } }
function addSubscriptions() { if (model.provider.isA('SelectionProvider')) { model.subscriptions.push( model.provider.onAnnotationChange((annotation) => { if (annotation.selection.type === 'partition') { const field = annotation.selection.partition.variable; if ( annotation.readOnly && model.readOnlyFields.indexOf(field) === -1 ) return; if (!annotation.readOnly && model.fieldData[field].lockAnnot) return;
model.fieldData[field].annotation = annotation; partitionToDividers( annotation, model.fieldData[field], model.scores );
publicAPI.render(field); } }) ); } }
function updateFieldAnnotations(fieldsData) { let fieldAnnotations = fieldsData; if (!fieldAnnotations && model.provider.getFieldPartitions) { fieldAnnotations = model.provider.getFieldPartitions(); } if (fieldAnnotations) { Object.keys(fieldAnnotations).forEach((field) => { const annotation = fieldAnnotations[field]; if (model.fieldData[field]) { model.fieldData[field].annotation = annotation; partitionToDividers(annotation, model.fieldData[field], model.scores); publicAPI.render(field); } }); } }
function clearFieldAnnotation(fieldName) { model.fieldData[fieldName].annotation = null; model.fieldData[fieldName].dividers = undefined; model.fieldData[fieldName].regions = [model.defaultScore]; model.fieldData[fieldName].editScore = false; }
return { addSubscriptions, createGroups, createHeader, createPopups, createScoreIcons, defaultFieldData, editingScore, enabled, filterFieldNames, getHistRange, init, numScoreIcons, prepareItem, rescaleDividers, updateHeader, updateFieldAnnotations, clearFieldAnnotation, updateScoreIcons, }; }
|