From f0289cb670fedde2dfcd7d6b4bbf3641f88b133a Mon Sep 17 00:00:00 2001 From: Jack Wilburn Date: Thu, 19 May 2022 12:23:47 -0600 Subject: [PATCH 01/14] Move position scale creation to separate function --- src/components/MultiLink.vue | 48 +++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/src/components/MultiLink.vue b/src/components/MultiLink.vue index 56535c01..2b70b631 100644 --- a/src/components/MultiLink.vue +++ b/src/components/MultiLink.vue @@ -10,7 +10,7 @@ import { select } from 'd3-selection'; import store from '@/store'; import { - Node, Edge, SimulationEdge, + Node, Edge, SimulationEdge, AttributeRange, } from '@/types'; import ContextMenu from '@/components/ContextMenu.vue'; @@ -20,6 +20,7 @@ import { } from '@vue/composition-api'; import { axisBottom, axisLeft } from 'd3-axis'; import { isInternalField } from '@/lib/typeUtils'; +import { ColumnType } from 'multinet'; export default defineComponent({ components: { @@ -592,6 +593,27 @@ export default defineComponent({ }); const layoutVars = computed(() => store.state.layoutVars); + function makePositionScale(axis: 'x' | 'y', type: ColumnType, range: AttributeRange, maxPosition: number, yAxisPadding: number) { + let positionScale; + + if (type === 'number') { + positionScale = scaleLinear() + .domain([range.min, range.max]); + } else { + positionScale = scaleBand() + .domain(range.binLabels); + } + + if (axis === 'x') { + positionScale = scaleLinear() + .range([yAxisPadding, maxPosition]); + } else { + positionScale = scaleBand() + .range([maxPosition, 10]); + } + + return positionScale; + } watch(layoutVars, () => { select('#axes').selectAll('g').remove(); const xAxisPadding = 60; @@ -603,17 +625,7 @@ export default defineComponent({ const range = store.state.attributeRanges[layoutVars.value.x]; const maxPosition = store.state.svgDimensions.width - 10; - let positionScale; - - if (type === 'number') { - positionScale = scaleLinear() - .domain([range.min, range.max]) - .range([yAxisPadding, maxPosition]); - } else { - positionScale = scaleBand() - .domain(range.binLabels) - .range([yAxisPadding, maxPosition]); - } + const positionScale = makePositionScale('x', type, range, maxPosition, yAxisPadding); // eslint-disable-next-line @typescript-eslint/no-explicit-any const xAxis = axisBottom(positionScale as any); @@ -654,17 +666,7 @@ export default defineComponent({ const range = store.state.attributeRanges[layoutVars.value.y]; const maxPosition = store.state.svgDimensions.height - xAxisPadding; - let positionScale; - - if (type === 'number') { - positionScale = scaleLinear() - .domain([range.min, range.max]) - .range([maxPosition, 10]); - } else { - positionScale = scaleBand() - .domain(range.binLabels) - .range([maxPosition, 10]); - } + const positionScale = makePositionScale('y', type, range, maxPosition, yAxisPadding); // eslint-disable-next-line @typescript-eslint/no-explicit-any const yAxis = axisLeft(positionScale as any); From 68bdeedc5fa581e81c970db5eb05b8f785bf4bdf Mon Sep 17 00:00:00 2001 From: Jack Wilburn Date: Thu, 19 May 2022 15:43:56 -0600 Subject: [PATCH 02/14] Don't overwrite the scale when setting range --- src/components/MultiLink.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MultiLink.vue b/src/components/MultiLink.vue index 2b70b631..928be8f6 100644 --- a/src/components/MultiLink.vue +++ b/src/components/MultiLink.vue @@ -605,10 +605,10 @@ export default defineComponent({ } if (axis === 'x') { - positionScale = scaleLinear() + positionScale = positionScale .range([yAxisPadding, maxPosition]); } else { - positionScale = scaleBand() + positionScale = positionScale .range([maxPosition, 10]); } From 4b2f84765f63a3e42dc4cdcb24ff8b0284b5bdf8 Mon Sep 17 00:00:00 2001 From: JackWilb Date: Thu, 16 Jun 2022 16:22:00 -0600 Subject: [PATCH 03/14] Move rendering logic for layoutVars to MultiLink.vue --- src/components/MultiLink.vue | 110 +++++++++++++++++++++++++++++++++-- src/store/index.ts | 80 +------------------------ 2 files changed, 107 insertions(+), 83 deletions(-) diff --git a/src/components/MultiLink.vue b/src/components/MultiLink.vue index 928be8f6..04c73256 100644 --- a/src/components/MultiLink.vue +++ b/src/components/MultiLink.vue @@ -594,22 +594,124 @@ export default defineComponent({ const layoutVars = computed(() => store.state.layoutVars); function makePositionScale(axis: 'x' | 'y', type: ColumnType, range: AttributeRange, maxPosition: number, yAxisPadding: number) { - let positionScale; + const varName = layoutVars.value[axis]; + let clipLow = false; + let clipHigh = true; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let positionScale: any; if (type === 'number') { + let minValue = range.min; + let maxValue = range.max; + + // Check IQR for outliers + if (network.value !== null && varName !== null) { + const values = network.value.nodes.map((node) => node[varName]).sort((a, b) => a - b); + + let q1; + let q3; + if ((values.length / 4) % 1 === 0) { + q1 = 0.5 * (values[(values.length / 4)] + values[(values.length / 4) + 1]); + q3 = 0.5 * (values[(values.length * (3 / 4))] + values[(values.length * (3 / 4)) + 1]); + } else { + q1 = values[Math.floor(values.length / 4 + 1)]; + q3 = values[Math.ceil(values.length * (3 / 4) + 1)]; + } + + const iqr = q3 - q1; + const maxCandidate = q3 + iqr * 1.5; + const minCandidate = q1 - iqr * 1.5; + if (maxCandidate < maxValue) { + maxValue = maxCandidate; + clipHigh = true; + + select(`#${axis}-high-clip`).style('visibility', 'visible'); + } + + if (minCandidate > minValue) { + minValue = minCandidate; + clipLow = true; + select(`#${axis}-low-clip`).style('visibility', 'visible'); + } + } + positionScale = scaleLinear() - .domain([range.min, range.max]); + .domain([minValue, maxValue]); } else { positionScale = scaleBand() .domain(range.binLabels); } if (axis === 'x') { + const minMax = [clipLow ? yAxisPadding + 50 : yAxisPadding, clipHigh ? maxPosition - 50 : maxPosition]; positionScale = positionScale - .range([yAxisPadding, maxPosition]); + .range(minMax); } else { + const minMax = [clipLow ? maxPosition + 50 : maxPosition, clipHigh ? 10 - 50 : 10]; positionScale = positionScale - .range([maxPosition, 10]); + .range(minMax); + } + + const otherAxis = axis === 'x' ? 'y' : 'x'; + + if (varName !== null) { + // Set node size smaller + store.commit.setMarkerSize({ markerSize: 10, updateProv: true }); + + // Clear the label variable + store.commit.setLabelVariable(undefined); + + store.commit.stopSimulation(); + + if (store.state.network !== null && store.state.columnTypes !== null) { + const otherAxisPadding = axis === 'x' ? 80 : 60; + + if (type === 'number') { + const scaleRange = positionScale.range(); + store.state.network.nodes.forEach((node) => { + let position = positionScale(node[varName]); + position = position > scaleRange[1] ? scaleRange[1] + 25 : position; + position = position < scaleRange[0] ? scaleRange[0] - 25 : position; + // eslint-disable-next-line no-param-reassign + node[axis] = position; + // eslint-disable-next-line no-param-reassign + node[`f${axis}`] = position; + + if (store.state.layoutVars[otherAxis] === null) { + const otherSvgDimension = axis === 'x' ? store.state.svgDimensions.height : store.state.svgDimensions.width; + // eslint-disable-next-line no-param-reassign + node[otherAxis] = otherSvgDimension / 2; + // eslint-disable-next-line no-param-reassign + node[`f${otherAxis}`] = otherSvgDimension / 2; + } + }); + } else { + let positionOffset: number; + + if (axis === 'x') { + positionOffset = (maxPosition - otherAxisPadding) / ((range.binLabels.length) * 2); + } else { + positionOffset = (maxPosition - 10) / ((range.binLabels.length) * 2); + } + + store.state.network.nodes.forEach((node) => { + // eslint-disable-next-line no-param-reassign + node[axis] = (positionScale(node[varName]) || 0) + positionOffset; + // eslint-disable-next-line no-param-reassign + node[`f${axis}`] = (positionScale(node[varName]) || 0) + positionOffset; + + if (store.state.layoutVars[otherAxis] === null) { + const otherSvgDimension = axis === 'x' ? store.state.svgDimensions.height : store.state.svgDimensions.width; + // eslint-disable-next-line no-param-reassign + node[otherAxis] = otherSvgDimension / 2; + // eslint-disable-next-line no-param-reassign + node[`f${otherAxis}`] = otherSvgDimension / 2; + } + }); + } + } + } else if (store.state.layoutVars[otherAxis] === null) { + store.dispatch.releaseNodes(); } return positionScale; diff --git a/src/store/index.ts b/src/store/index.ts index 74e317c2..10276c84 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -11,7 +11,7 @@ import { import api from '@/api'; import { ColumnTypes, NetworkSpec, UserSpec } from 'multinet'; import { - ScaleBand, scaleBand, ScaleLinear, scaleLinear, scaleOrdinal, scaleSequential, + scaleLinear, scaleOrdinal, scaleSequential, } from 'd3-scale'; import { interpolateBlues, interpolateReds, schemeCategory10 } from 'd3-scale-chromatic'; import { initProvenance, Provenance } from '@visdesignlab/trrack'; @@ -560,84 +560,6 @@ const { } = payload; const otherAxis = axis === 'x' ? 'y' : 'x'; - if (varName !== null) { - // Set node size smaller - commit.setMarkerSize({ markerSize: 10, updateProv: true }); - - // Clear the label variable - commit.setLabelVariable(undefined); - - commit.stopSimulation(); - - if (state.network !== null && state.columnTypes !== null) { - const type = state.columnTypes[varName]; - const range = state.attributeRanges[varName]; - const otherAxisPadding = axis === 'x' ? 80 : 60; - const maxPosition = axis === 'x' ? state.svgDimensions.width - 10 : state.svgDimensions.height - otherAxisPadding - state.markerSize; - - if (type === 'number') { - let positionScale: ScaleLinear; - - if (axis === 'x') { - positionScale = scaleLinear() - .domain([range.min, range.max]) - .range([otherAxisPadding, maxPosition]); - } else { - positionScale = scaleLinear() - .domain([range.min, range.max]) - .range([maxPosition, 10]); - } - - state.network.nodes.forEach((node) => { - // eslint-disable-next-line no-param-reassign - node[axis] = positionScale(node[varName]); - // eslint-disable-next-line no-param-reassign - node[`f${axis}`] = positionScale(node[varName]); - - if (state.layoutVars[otherAxis] === null) { - const otherSvgDimension = axis === 'x' ? state.svgDimensions.height : state.svgDimensions.width; - // eslint-disable-next-line no-param-reassign - node[otherAxis] = otherSvgDimension / 2; - // eslint-disable-next-line no-param-reassign - node[`f${otherAxis}`] = otherSvgDimension / 2; - } - }); - } else { - let positionScale: ScaleBand; - let positionOffset: number; - - if (axis === 'x') { - positionScale = scaleBand() - .domain(range.binLabels) - .range([otherAxisPadding, maxPosition]); - positionOffset = (maxPosition - otherAxisPadding) / ((range.binLabels.length) * 2); - } else { - positionScale = scaleBand() - .domain(range.binLabels) - .range([maxPosition, 10]); - positionOffset = (maxPosition - 10) / ((range.binLabels.length) * 2); - } - - state.network.nodes.forEach((node) => { - // eslint-disable-next-line no-param-reassign - node[axis] = (positionScale(node[varName]) || 0) + positionOffset; - // eslint-disable-next-line no-param-reassign - node[`f${axis}`] = (positionScale(node[varName]) || 0) + positionOffset; - - if (state.layoutVars[otherAxis] === null) { - const otherSvgDimension = axis === 'x' ? state.svgDimensions.height : state.svgDimensions.width; - // eslint-disable-next-line no-param-reassign - node[otherAxis] = otherSvgDimension / 2; - // eslint-disable-next-line no-param-reassign - node[`f${otherAxis}`] = otherSvgDimension / 2; - } - }); - } - } - } else if (state.layoutVars[otherAxis] === null) { - dispatch.releaseNodes(); - } - const updatedLayoutVars = { [axis]: varName, [otherAxis]: state.layoutVars[otherAxis] } as { x: string | null; y: string | null; From 718bf9d47203373a0eb8f3eef95b0391597adcc6 Mon Sep 17 00:00:00 2001 From: JackWilb Date: Thu, 16 Jun 2022 16:22:21 -0600 Subject: [PATCH 04/14] Add clip regions to the html --- src/components/MultiLink.vue | 55 +++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/src/components/MultiLink.vue b/src/components/MultiLink.vue index 04c73256..fffb9611 100644 --- a/src/components/MultiLink.vue +++ b/src/components/MultiLink.vue @@ -716,8 +716,19 @@ export default defineComponent({ return positionScale; } - watch(layoutVars, () => { + + function resetAxesClipRegions() { select('#axes').selectAll('g').remove(); + + select('#x-low-clip').style('visibility', 'hidden'); + select('#x-high-clip').style('visibility', 'hidden'); + select('#y-low-clip').style('visibility', 'hidden'); + select('#y-high-clip').style('visibility', 'hidden'); + } + + watch(layoutVars, () => { + resetAxesClipRegions(); + const xAxisPadding = 60; const yAxisPadding = 80; @@ -867,6 +878,42 @@ export default defineComponent({ + + + + + + + + From db733973ab27df38d758a12be45763248e641b2d Mon Sep 17 00:00:00 2001 From: JackWilb Date: Thu, 16 Jun 2022 16:59:31 -0600 Subject: [PATCH 05/14] Add variable to track clipRegion size --- src/components/MultiLink.vue | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/components/MultiLink.vue b/src/components/MultiLink.vue index fffb9611..afc82ef5 100644 --- a/src/components/MultiLink.vue +++ b/src/components/MultiLink.vue @@ -68,6 +68,7 @@ export default defineComponent({ const controlsWidth = computed(() => store.state.controlsWidth); const directionalEdges = computed(() => store.state.directionalEdges); const edgeColorScale = computed(() => store.getters.edgeColorScale); + const clipRegionSize = 100; // Update height and width as the window size changes // Also update center attraction forces as the size changes @@ -621,6 +622,7 @@ export default defineComponent({ const iqr = q3 - q1; const maxCandidate = q3 + iqr * 1.5; const minCandidate = q1 - iqr * 1.5; + if (maxCandidate < maxValue) { maxValue = maxCandidate; clipHigh = true; @@ -643,11 +645,11 @@ export default defineComponent({ } if (axis === 'x') { - const minMax = [clipLow ? yAxisPadding + 50 : yAxisPadding, clipHigh ? maxPosition - 50 : maxPosition]; + const minMax = [clipLow ? yAxisPadding + clipRegionSize : yAxisPadding, clipHigh ? maxPosition - clipRegionSize : maxPosition]; positionScale = positionScale .range(minMax); } else { - const minMax = [clipLow ? maxPosition + 50 : maxPosition, clipHigh ? 10 - 50 : 10]; + const minMax = [clipLow ? maxPosition - clipRegionSize : maxPosition, clipHigh ? 10 + clipRegionSize : 10]; positionScale = positionScale .range(minMax); } @@ -670,8 +672,10 @@ export default defineComponent({ const scaleRange = positionScale.range(); store.state.network.nodes.forEach((node) => { let position = positionScale(node[varName]); - position = position > scaleRange[1] ? scaleRange[1] + 25 : position; - position = position < scaleRange[0] ? scaleRange[0] - 25 : position; + if (axis === 'x') { + position = position > scaleRange[1] ? scaleRange[1] + (clipRegionSize / 2) + 10 : position; + position = position < scaleRange[0] ? scaleRange[0] - (clipRegionSize / 2) - 10 : position; + } // eslint-disable-next-line no-param-reassign node[axis] = position; // eslint-disable-next-line no-param-reassign @@ -849,6 +853,7 @@ export default defineComponent({ nestedPadding, nodeBarColorScale, glyphFill, + clipRegionSize, }; }, }); @@ -886,22 +891,22 @@ export default defineComponent({ x="0" y="0" :height="svgDimensions.height" - width="50" + :width="clipRegionSize" /> From c3539cc3f6e073db4627b9435ac99894b34312bd Mon Sep 17 00:00:00 2001 From: JackWilb Date: Thu, 16 Jun 2022 16:59:56 -0600 Subject: [PATCH 06/14] Fix the y axis layout logic --- src/components/MultiLink.vue | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/MultiLink.vue b/src/components/MultiLink.vue index afc82ef5..3d626fd1 100644 --- a/src/components/MultiLink.vue +++ b/src/components/MultiLink.vue @@ -675,7 +675,12 @@ export default defineComponent({ if (axis === 'x') { position = position > scaleRange[1] ? scaleRange[1] + (clipRegionSize / 2) + 10 : position; position = position < scaleRange[0] ? scaleRange[0] - (clipRegionSize / 2) - 10 : position; + } else { + position = position < scaleRange[1] ? scaleRange[1] - (clipRegionSize / 2) - 10 : position; + position = position > scaleRange[0] ? scaleRange[0] + (clipRegionSize / 2) + 10 : position; } + position -= (markerSize.value / 2); + // eslint-disable-next-line no-param-reassign node[axis] = position; // eslint-disable-next-line no-param-reassign From c0fdbf80de854c6e73206ffb7e09b8ac63187956 Mon Sep 17 00:00:00 2001 From: JackWilb Date: Sun, 26 Jun 2022 17:58:24 -0600 Subject: [PATCH 07/14] Release nodes when both axes are cleared --- src/store/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/store/index.ts b/src/store/index.ts index 10276c84..7ee2d7ab 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -572,6 +572,9 @@ const { commit.setMarkerSize({ markerSize: 11, updateProv: false }); dispatch.applyVariableLayout({ varName: state.layoutVars[otherAxis], axis: otherAxis }); + } else if (varName === null && state.layoutVars[otherAxis] === null) { + // If both null, release + dispatch.releaseNodes(); } }, }, From 1046d5484df0682cfc38f8256a25a4771e162ce7 Mon Sep 17 00:00:00 2001 From: JackWilb Date: Sun, 26 Jun 2022 18:00:40 -0600 Subject: [PATCH 08/14] Don't clip if we don't need to --- src/components/MultiLink.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MultiLink.vue b/src/components/MultiLink.vue index 3d626fd1..f8c8732f 100644 --- a/src/components/MultiLink.vue +++ b/src/components/MultiLink.vue @@ -597,7 +597,7 @@ export default defineComponent({ function makePositionScale(axis: 'x' | 'y', type: ColumnType, range: AttributeRange, maxPosition: number, yAxisPadding: number) { const varName = layoutVars.value[axis]; let clipLow = false; - let clipHigh = true; + let clipHigh = false; // eslint-disable-next-line @typescript-eslint/no-explicit-any let positionScale: any; From 6e0e413f8c137f5fee33c65783abc0040f317d1b Mon Sep 17 00:00:00 2001 From: JackWilb Date: Sun, 26 Jun 2022 18:53:42 -0600 Subject: [PATCH 09/14] Refactor maxPosition, x/yAxisPading to fix low clipRegion position --- src/components/MultiLink.vue | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/components/MultiLink.vue b/src/components/MultiLink.vue index f8c8732f..e15b4477 100644 --- a/src/components/MultiLink.vue +++ b/src/components/MultiLink.vue @@ -593,8 +593,10 @@ export default defineComponent({ } }); + const xAxisPadding = 60; + const yAxisPadding = 80; const layoutVars = computed(() => store.state.layoutVars); - function makePositionScale(axis: 'x' | 'y', type: ColumnType, range: AttributeRange, maxPosition: number, yAxisPadding: number) { + function makePositionScale(axis: 'x' | 'y', type: ColumnType, range: AttributeRange) { const varName = layoutVars.value[axis]; let clipLow = false; let clipHigh = false; @@ -645,11 +647,11 @@ export default defineComponent({ } if (axis === 'x') { - const minMax = [clipLow ? yAxisPadding + clipRegionSize : yAxisPadding, clipHigh ? maxPosition - clipRegionSize : maxPosition]; + const minMax = [clipLow ? yAxisPadding + clipRegionSize : yAxisPadding, clipHigh ? store.state.svgDimensions.width - clipRegionSize : store.state.svgDimensions.width]; positionScale = positionScale .range(minMax); } else { - const minMax = [clipLow ? maxPosition - clipRegionSize : maxPosition, clipHigh ? 10 + clipRegionSize : 10]; + const minMax = [clipLow ? store.state.svgDimensions.height - xAxisPadding - clipRegionSize : store.state.svgDimensions.height - xAxisPadding, clipHigh ? clipRegionSize : 0]; positionScale = positionScale .range(minMax); } @@ -698,9 +700,9 @@ export default defineComponent({ let positionOffset: number; if (axis === 'x') { - positionOffset = (maxPosition - otherAxisPadding) / ((range.binLabels.length) * 2); + positionOffset = (store.state.svgDimensions.width - otherAxisPadding) / ((range.binLabels.length) * 2); } else { - positionOffset = (maxPosition - 10) / ((range.binLabels.length) * 2); + positionOffset = (store.state.svgDimensions.height - xAxisPadding - 10) / ((range.binLabels.length) * 2); } store.state.network.nodes.forEach((node) => { @@ -738,16 +740,12 @@ export default defineComponent({ watch(layoutVars, () => { resetAxesClipRegions(); - const xAxisPadding = 60; - const yAxisPadding = 80; - // Add x layout if (store.state.columnTypes !== null && layoutVars.value.x !== null) { const type = store.state.columnTypes[layoutVars.value.x]; const range = store.state.attributeRanges[layoutVars.value.x]; - const maxPosition = store.state.svgDimensions.width - 10; - const positionScale = makePositionScale('x', type, range, maxPosition, yAxisPadding); + const positionScale = makePositionScale('x', type, range); // eslint-disable-next-line @typescript-eslint/no-explicit-any const xAxis = axisBottom(positionScale as any); @@ -769,7 +767,7 @@ export default defineComponent({ .attr('fill', 'currentColor') .attr('font-size', '14px') .attr('font-weight', 'bold') - .attr('x', ((maxPosition - yAxisPadding) / 2) + yAxisPadding) + .attr('x', ((store.state.svgDimensions.width - yAxisPadding) / 2) + yAxisPadding) .attr('y', xAxisPadding - 20); const labelRectPos = (label.node() as SVGTextElement).getBBox(); @@ -786,9 +784,8 @@ export default defineComponent({ if (store.state.columnTypes !== null && layoutVars.value.y !== null) { const type = store.state.columnTypes[layoutVars.value.y]; const range = store.state.attributeRanges[layoutVars.value.y]; - const maxPosition = store.state.svgDimensions.height - xAxisPadding; - const positionScale = makePositionScale('y', type, range, maxPosition, yAxisPadding); + const positionScale = makePositionScale('y', type, range); // eslint-disable-next-line @typescript-eslint/no-explicit-any const yAxis = axisLeft(positionScale as any); @@ -812,7 +809,7 @@ export default defineComponent({ .attr('font-size', '14px') .attr('font-weight', 'bold') .attr('text-anchor', 'middle') - .attr('x', ((maxPosition - 10) / 2) + 10) + .attr('x', ((store.state.svgDimensions.height - xAxisPadding - 10) / 2) + 10) .attr('y', yAxisPadding - 20); const labelRectPos = (label.node() as SVGTextElement).getBBox(); @@ -859,6 +856,8 @@ export default defineComponent({ nodeBarColorScale, glyphFill, clipRegionSize, + xAxisPadding, + yAxisPadding, }; }, }); @@ -893,7 +892,7 @@ export default defineComponent({ From a5ee78b3c39e3982a54b34b46c6c87778d6797be Mon Sep 17 00:00:00 2001 From: JackWilb Date: Sun, 26 Jun 2022 18:54:23 -0600 Subject: [PATCH 10/14] Use domain instead of range for clip detection --- src/components/MultiLink.vue | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/MultiLink.vue b/src/components/MultiLink.vue index e15b4477..eeb0beac 100644 --- a/src/components/MultiLink.vue +++ b/src/components/MultiLink.vue @@ -605,7 +605,7 @@ export default defineComponent({ if (type === 'number') { let minValue = range.min; - let maxValue = range.max; + let maxValue = range.max - 1; // subtract 1, because of the + 1 on the legend chart scale // Check IQR for outliers if (network.value !== null && varName !== null) { @@ -671,15 +671,17 @@ export default defineComponent({ const otherAxisPadding = axis === 'x' ? 80 : 60; if (type === 'number') { + const scaleDomain = positionScale.domain(); const scaleRange = positionScale.range(); store.state.network.nodes.forEach((node) => { - let position = positionScale(node[varName]); + const nodeVal = node[varName]; + let position = positionScale(nodeVal); if (axis === 'x') { - position = position > scaleRange[1] ? scaleRange[1] + (clipRegionSize / 2) + 10 : position; - position = position < scaleRange[0] ? scaleRange[0] - (clipRegionSize / 2) - 10 : position; + position = nodeVal > scaleDomain[1] ? scaleRange[1] + (clipRegionSize / 2) : position; + position = nodeVal < scaleDomain[0] ? scaleRange[0] - (clipRegionSize / 2) : position; } else { - position = position < scaleRange[1] ? scaleRange[1] - (clipRegionSize / 2) - 10 : position; - position = position > scaleRange[0] ? scaleRange[0] + (clipRegionSize / 2) + 10 : position; + position = nodeVal > scaleDomain[1] ? scaleRange[1] - (clipRegionSize / 2) : position; + position = nodeVal < scaleDomain[0] ? scaleRange[0] + (clipRegionSize / 2) : position; } position -= (markerSize.value / 2); From baa7664becf08ffec4c1d2c772614d4f437b1ac0 Mon Sep 17 00:00:00 2001 From: JackWilb Date: Sun, 26 Jun 2022 18:54:37 -0600 Subject: [PATCH 11/14] Remove a blank line --- src/components/MultiLink.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/MultiLink.vue b/src/components/MultiLink.vue index eeb0beac..01676547 100644 --- a/src/components/MultiLink.vue +++ b/src/components/MultiLink.vue @@ -628,7 +628,6 @@ export default defineComponent({ if (maxCandidate < maxValue) { maxValue = maxCandidate; clipHigh = true; - select(`#${axis}-high-clip`).style('visibility', 'visible'); } From d28d72ed845b2f77291d7c0e28f18cce4dfa4955 Mon Sep 17 00:00:00 2001 From: JackWilb Date: Sun, 26 Jun 2022 19:31:10 -0600 Subject: [PATCH 12/14] Add clip region labels --- src/components/MultiLink.vue | 102 +++++++++++++++++++++++++---------- 1 file changed, 73 insertions(+), 29 deletions(-) diff --git a/src/components/MultiLink.vue b/src/components/MultiLink.vue index 01676547..0a02668c 100644 --- a/src/components/MultiLink.vue +++ b/src/components/MultiLink.vue @@ -629,12 +629,14 @@ export default defineComponent({ maxValue = maxCandidate; clipHigh = true; select(`#${axis}-high-clip`).style('visibility', 'visible'); + select(`#${axis}-high-clip > text`).text(`nodes > ${maxCandidate}`); } if (minCandidate > minValue) { minValue = minCandidate; clipLow = true; select(`#${axis}-low-clip`).style('visibility', 'visible'); + select(`#${axis}-low-clip > text`).text(`nodes < ${minCandidate}`); } } @@ -890,38 +892,81 @@ export default defineComponent({ - - + + low values + + + + - + + high values + + + + - + + low values + + + + + > + + high values + + Date: Mon, 27 Jun 2022 14:03:49 -0600 Subject: [PATCH 13/14] Layout the clipped nodes in the order they show up in the data --- src/components/MultiLink.vue | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/MultiLink.vue b/src/components/MultiLink.vue index 0a02668c..2eddf9a7 100644 --- a/src/components/MultiLink.vue +++ b/src/components/MultiLink.vue @@ -677,12 +677,13 @@ export default defineComponent({ store.state.network.nodes.forEach((node) => { const nodeVal = node[varName]; let position = positionScale(nodeVal); + if (axis === 'x') { - position = nodeVal > scaleDomain[1] ? scaleRange[1] + (clipRegionSize / 2) : position; - position = nodeVal < scaleDomain[0] ? scaleRange[0] - (clipRegionSize / 2) : position; + position = nodeVal > scaleDomain[1] ? scaleRange[1] + ((clipRegionSize - 10) * ((nodeVal - scaleDomain[1]) / (range.max - 1 - scaleDomain[1]))) : position; + position = nodeVal < scaleDomain[0] ? scaleRange[0] - ((clipRegionSize - 10) * ((scaleDomain[0] - nodeVal) / (scaleDomain[0] - range.min))) : position; } else { - position = nodeVal > scaleDomain[1] ? scaleRange[1] - (clipRegionSize / 2) : position; - position = nodeVal < scaleDomain[0] ? scaleRange[0] + (clipRegionSize / 2) : position; + position = nodeVal > scaleDomain[1] ? scaleRange[1] - ((clipRegionSize - 10) * ((nodeVal - scaleDomain[1]) / (range.max - 1 - scaleDomain[1]))) : position; + position = nodeVal < scaleDomain[0] ? scaleRange[0] + ((clipRegionSize - 10) * ((scaleDomain[0] - nodeVal) / (scaleDomain[0] - range.min))) : position; } position -= (markerSize.value / 2); From 4fd664bcfb4ca3c07dc105db85cd6c19a214c1f4 Mon Sep 17 00:00:00 2001 From: JackWilb Date: Mon, 27 Jun 2022 14:18:25 -0600 Subject: [PATCH 14/14] Remove 'nodes' from clipped area labels --- src/components/MultiLink.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MultiLink.vue b/src/components/MultiLink.vue index 2eddf9a7..a2f65e20 100644 --- a/src/components/MultiLink.vue +++ b/src/components/MultiLink.vue @@ -629,14 +629,14 @@ export default defineComponent({ maxValue = maxCandidate; clipHigh = true; select(`#${axis}-high-clip`).style('visibility', 'visible'); - select(`#${axis}-high-clip > text`).text(`nodes > ${maxCandidate}`); + select(`#${axis}-high-clip > text`).text(`> ${maxCandidate}`); } if (minCandidate > minValue) { minValue = minCandidate; clipLow = true; select(`#${axis}-low-clip`).style('visibility', 'visible'); - select(`#${axis}-low-clip > text`).text(`nodes < ${minCandidate}`); + select(`#${axis}-low-clip > text`).text(`< ${minCandidate}`); } }