Skip to content

Commit aae1360

Browse files
committed
New feature - VueUiScatter - Add optional marginal bars highlighters
1 parent 24fbc94 commit aae1360

File tree

4 files changed

+194
-55
lines changed

4 files changed

+194
-55
lines changed

TestingArena/ArenaVueUiScatter.vue

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,15 @@ const model = ref([
143143
{ key: 'style.layout.marginalBars.useGradient', def: true, type: 'checkbox'},
144144
{ key: 'style.layout.marginalBars.showLines', def: true, type: 'checkbox'},
145145
{ key: 'style.layout.marginalBars.linesStrokeWidth', def: 1, type: 'number', min: 0.5, max: 12, step: 0.5},
146+
147+
{ key: 'style.layout.marginalBars.highlighter.show', def: true, type: 'checkbox'},
148+
{ key: 'style.layout.marginalBars.highlighter.opacity', def: 0.1, type: 'range', min: 0, max: 1, step: 0.01},
149+
{ key: 'style.layout.marginalBars.highlighter.color', def: '#1A1A1A', type: 'color'},
150+
{ key: 'style.layout.marginalBars.highlighter.stroke', def: '#1A1A1A', type: 'color'},
151+
{ key: 'style.layout.marginalBars.highlighter.strokeWidth', def: 0.5, type: 'number', min: 0, max: 12, step: 0.1},
152+
{ key: 'style.layout.marginalBars.highlighter.strokeDasharray', def: 2, type: 'number', min: 0, max: 12},
153+
{ key: 'style.layout.marginalBars.highlighter.highlightBothAxes', def: false, type: 'checkbox'},
154+
146155
{ key: 'style.layout.plots.radius', def: 2, type: 'number', min: 0, max: 24},
147156
{ key: 'style.layout.plots.stroke', def: '#FFFFFF', type: 'color'},
148157
{ key: 'style.layout.plots.strokeWidth', def: 0.3, type: 'range', min: 0.1, max: 12, step: 0.1},
@@ -353,9 +362,9 @@ onMounted(async () => {
353362
responsive: true
354363
}">
355364

356-
<template #chart-background>
365+
<!-- <template #chart-background>
357366
<div style="width: 100%; height: 100%; background: radial-gradient(at top left, red, white)"/>
358-
</template>
367+
</template> -->
359368

360369
<template #watermark="{ isPrinting }">
361370
<div v-if="isPrinting" style="font-size: 100px; opacity: 0.1; transform: rotate(-10deg)">

src/components/vue-ui-scatter.vue

Lines changed: 164 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ const tableStep = ref(0);
9292
const legendStep = ref(0);
9393
const segregated = ref([]);
9494
const readyTeleport = ref(false);
95+
const selectedMarginalX = ref(null);
96+
const selectedMarginalY = ref(null);
9597
9698
const xAxisLabelLeft = ref(null);
9799
const xAxisLabelRight = ref(null);
@@ -923,6 +925,25 @@ async function getImage({ scale = 2} = {}) {
923925
}
924926
}
925927
928+
function onMarginalXEnter(index) {
929+
selectedMarginalX.value = index;
930+
if (FINAL_CONFIG.value.style.layout.marginalBars.highlighter.highlightBothAxes) {
931+
selectedMarginalY.value = marginalBars.value.y.length - 2 - index;
932+
}
933+
}
934+
935+
function onMarginalYEnter(index) {
936+
selectedMarginalY.value = index;
937+
if (FINAL_CONFIG.value.style.layout.marginalBars.highlighter.highlightBothAxes) {
938+
selectedMarginalX.value = index;
939+
}
940+
}
941+
942+
function onMarginalLeave() {
943+
selectedMarginalX.value = null;
944+
selectedMarginalY.value = null;
945+
}
946+
926947
defineExpose({
927948
getData,
928949
getImage,
@@ -1043,6 +1064,7 @@ defineExpose({
10431064
:class="{ 'vue-data-ui-fullscreen--on': isFullscreen, 'vue-data-ui-fulscreen--off': !isFullscreen, 'animated': FINAL_CONFIG.useCssAnimation }"
10441065
:viewBox="`0 0 ${svg.width <= 0 ? 10 : svg.width} ${svg.height <= 0 ? 10 : svg.height}`"
10451066
:style="`max-width:100%;overflow:visible;background:transparent;color:${FINAL_CONFIG.style.color}`"
1067+
@mouseleave="onMarginalLeave"
10461068
>
10471069
<PackageVersion />
10481070
@@ -1084,57 +1106,6 @@ defineExpose({
10841106
/>
10851107
</g>
10861108
1087-
<!-- GIFT WRAP -->
1088-
<g v-if="FINAL_CONFIG.style.layout.plots.giftWrap.show">
1089-
<g v-for="(ds, i) in drawableDataset">
1090-
<polygon
1091-
v-if="ds.plots.length > 2"
1092-
:points="giftWrap({series: ds.plots})"
1093-
:fill="setOpacity(ds.color, FINAL_CONFIG.style.layout.plots.giftWrap.fillOpacity * 100)"
1094-
:stroke-width="FINAL_CONFIG.style.layout.plots.giftWrap.strokeWidth"
1095-
:stroke-dasharray="FINAL_CONFIG.style.layout.plots.giftWrap.strokeDasharray"
1096-
:stroke="ds.color"
1097-
stroke-linejoin="round"
1098-
stroke-linecap="round"
1099-
/>
1100-
</g>
1101-
</g>
1102-
1103-
<!-- PLOTS -->
1104-
<g v-for="(ds, i) in drawableDataset">
1105-
<g v-if="!ds.shape || ds.shape === 'circle'">
1106-
<circle
1107-
v-for="(plot, j) in ds.plots"
1108-
data-cy="atom-shape"
1109-
:cx="plot.x"
1110-
:cy="plot.y"
1111-
:r="selectedPlotId && selectedPlotId === plot.id ? plot.weight * 2 : plot.weight"
1112-
:fill="setOpacity(ds.color, FINAL_CONFIG.style.layout.plots.opacity * 100)"
1113-
:stroke="FINAL_CONFIG.style.layout.plots.stroke"
1114-
:stroke-width="FINAL_CONFIG.style.layout.plots.strokeWidth"
1115-
:style="`opacity:${selectedPlotId && selectedPlotId === plot.id ? 1 : FINAL_CONFIG.style.layout.plots.significance.useDistanceOpacity ? (1 - (Math.abs(plot.deviation) / maxDeviation)) : FINAL_CONFIG.style.layout.plots.significance.show && Math.abs(plot.deviation) > FINAL_CONFIG.style.layout.plots.significance.deviationThreshold ? FINAL_CONFIG.style.layout.plots.significance.opacity : 1}`"
1116-
@mouseover="onTrapEnter(plot, i)"
1117-
@mouseleave="onTrapLeave(plot, i)"
1118-
@click="onTrapClick(plot, i)"
1119-
/>
1120-
</g>
1121-
<g v-else>
1122-
<Shape
1123-
v-for="(plot, j) in ds.plots"
1124-
:plot="{x: plot.x, y: plot.y }"
1125-
:radius="selectedPlotId && selectedPlotId === plot.id ? plot.weight * 2 : plot.weight"
1126-
:shape="ds.shape"
1127-
:color="setOpacity(ds.color, FINAL_CONFIG.style.layout.plots.opacity * 100)"
1128-
:stroke="FINAL_CONFIG.style.layout.plots.stroke"
1129-
:strokeWidth="FINAL_CONFIG.style.layout.plots.strokeWidth"
1130-
:style="`opacity:${selectedPlotId && selectedPlotId === plot.id ? 1 : FINAL_CONFIG.style.layout.plots.significance.useDistanceOpacity ? (1 - (Math.abs(plot.deviation) / maxDeviation)) : FINAL_CONFIG.style.layout.plots.significance.show && Math.abs(plot.deviation) > FINAL_CONFIG.style.layout.plots.significance.deviationThreshold ? FINAL_CONFIG.style.layout.plots.significance.opacity : 1}`"
1131-
@mouseover="onTrapEnter(plot, i)"
1132-
@mouseleave="onTrapLeave(plot, i)"
1133-
@click="onTrapClick(plot, i)"
1134-
/>
1135-
</g>
1136-
</g>
1137-
11381109
<!-- MARGINAL BARS -->
11391110
<g v-if="FINAL_CONFIG.style.layout.marginalBars.show">
11401111
<defs>
@@ -1160,7 +1131,52 @@ defineExpose({
11601131
:stroke="FINAL_CONFIG.style.backgroundColor"
11611132
:stroke-width="FINAL_CONFIG.style.layout.marginalBars.strokeWidth"
11621133
:rx="FINAL_CONFIG.style.layout.marginalBars.borderRadius"
1134+
style="pointer-events: none"
11631135
/>
1136+
<!-- MARGINAL MOUSE TRAP (X) -->
1137+
<rect
1138+
v-if="marginalBars.avgX[i]"
1139+
:x="marginalBars.avgX[i] - (drawingArea.width / scale / 2)"
1140+
:y="drawingArea.top - FINAL_CONFIG.style.layout.marginalBars.offset - FINAL_CONFIG.style.layout.marginalBars.size"
1141+
:width="drawingArea.width / scale <= 0 ? 0.0001 : drawingArea.width / scale"
1142+
:height="Math.max(0.1, FINAL_CONFIG.style.layout.marginalBars.size)"
1143+
fill="transparent"
1144+
@mouseenter="onMarginalXEnter(i)"
1145+
@mouseleave="onMarginalLeave()"
1146+
/>
1147+
<!-- MARGINAL HIGHLIGHTER (X) -->
1148+
<template v-if="marginalBars.avgX[i] && selectedMarginalX != null && selectedMarginalX === i">
1149+
<g style="pointer-events: none;">
1150+
<rect
1151+
:x="marginalBars.avgX[i] - (drawingArea.width / scale / 2)"
1152+
:y="drawingArea.top"
1153+
:width="drawingArea.width / scale <= 0 ? 0.0001 : drawingArea.width / scale"
1154+
:height="drawingArea.height"
1155+
:fill="FINAL_CONFIG.style.layout.marginalBars.highlighter.color"
1156+
:fill-opacity="FINAL_CONFIG.style.layout.marginalBars.highlighter.opacity"
1157+
/>
1158+
<line
1159+
:x1="marginalBars.avgX[i] - (drawingArea.width / scale / 2)"
1160+
:x2="marginalBars.avgX[i] - (drawingArea.width / scale / 2)"
1161+
:y1="0"
1162+
:y2="drawingArea.top + drawingArea.height"
1163+
:stroke="FINAL_CONFIG.style.layout.marginalBars.highlighter.stroke"
1164+
:stroke-dasharray="FINAL_CONFIG.style.layout.marginalBars.highlighter.strokeDasharray"
1165+
:stroke-width="FINAL_CONFIG.style.layout.marginalBars.highlighter.strokeWidth"
1166+
:style="{ transition: 'none !important', animation: 'none !important' }"
1167+
/>
1168+
<line
1169+
:x1="marginalBars.avgX[i] - (drawingArea.width / scale / 2) + (drawingArea.width / scale <= 0 ? 0.0001 : drawingArea.width / scale)"
1170+
:x2="marginalBars.avgX[i] - (drawingArea.width / scale / 2) + (drawingArea.width / scale <= 0 ? 0.0001 : drawingArea.width / scale)"
1171+
:y1="0"
1172+
:y2="drawingArea.top + drawingArea.height"
1173+
:stroke="FINAL_CONFIG.style.layout.marginalBars.highlighter.stroke"
1174+
:stroke-dasharray="FINAL_CONFIG.style.layout.marginalBars.highlighter.strokeDasharray"
1175+
:stroke-width="FINAL_CONFIG.style.layout.marginalBars.highlighter.strokeWidth"
1176+
:style="{ transition: 'none !important', animation: 'none !important' }"
1177+
/>
1178+
</g>
1179+
</template>
11641180
</g>
11651181
<g v-for="(y, i) in marginalBars.y">
11661182
<rect
@@ -1175,9 +1191,54 @@ defineExpose({
11751191
:stroke="FINAL_CONFIG.style.backgroundColor"
11761192
:stroke-width="FINAL_CONFIG.style.layout.marginalBars.strokeWidth"
11771193
:rx="FINAL_CONFIG.style.layout.marginalBars.borderRadius"
1194+
style="pointer-events: none"
1195+
/>
1196+
<!-- MARGINAL MOUSE TRAP (Y) -->
1197+
<rect
1198+
v-if="marginalBars.avgY[i]"
1199+
:x="drawingArea.right + FINAL_CONFIG.style.layout.marginalBars.offset"
1200+
:y="marginalBars.avgY[i] - (drawingArea.height / scale / 2)"
1201+
:width="Math.max(0.1, FINAL_CONFIG.style.layout.marginalBars.size)"
1202+
:height="drawingArea.height / scale <= 0 ? 0.0001 : drawingArea.height / scale"
1203+
fill="transparent"
1204+
@mouseenter="onMarginalYEnter(i)"
1205+
@mouseleave="onMarginalLeave()"
11781206
/>
1207+
<!-- MARGINAL HIGHLIGHTER (X) -->
1208+
<template v-if="marginalBars.avgY[i] && selectedMarginalY != null && selectedMarginalY === i">
1209+
<g style="pointer-events: none;">
1210+
<rect
1211+
:x="drawingArea.left"
1212+
:y="marginalBars.avgY[i] - (drawingArea.height / scale / 2)"
1213+
:width="drawingArea.width"
1214+
:height="drawingArea.height / scale <= 0 ? 0.0001 : drawingArea.height / scale"
1215+
:fill="FINAL_CONFIG.style.layout.marginalBars.highlighter.color"
1216+
:fill-opacity="FINAL_CONFIG.style.layout.marginalBars.highlighter.opacity"
1217+
/>
1218+
<line
1219+
:x1="drawingArea.left"
1220+
:x2="svg.width"
1221+
:y1="marginalBars.avgY[i] - (drawingArea.height / scale / 2)"
1222+
:y2="marginalBars.avgY[i] - (drawingArea.height / scale / 2)"
1223+
:stroke="FINAL_CONFIG.style.layout.marginalBars.highlighter.stroke"
1224+
:stroke-dasharray="FINAL_CONFIG.style.layout.marginalBars.highlighter.strokeDasharray"
1225+
:stroke-width="FINAL_CONFIG.style.layout.marginalBars.highlighter.strokeWidth"
1226+
:style="{ transition: 'none !important', animation: 'none !important' }"
1227+
/>
1228+
<line
1229+
:x1="drawingArea.left"
1230+
:x2="svg.width"
1231+
:y1="marginalBars.avgY[i] - (drawingArea.height / scale / 2) + (drawingArea.height / scale <= 0 ? 0.0001 : drawingArea.height / scale)"
1232+
:y2="marginalBars.avgY[i] - (drawingArea.height / scale / 2) + (drawingArea.height / scale <= 0 ? 0.0001 : drawingArea.height / scale)"
1233+
:stroke="FINAL_CONFIG.style.layout.marginalBars.highlighter.stroke"
1234+
:stroke-dasharray="FINAL_CONFIG.style.layout.marginalBars.highlighter.strokeDasharray"
1235+
:stroke-width="FINAL_CONFIG.style.layout.marginalBars.highlighter.strokeWidth"
1236+
:style="{ transition: 'none !important', animation: 'none !important' }"
1237+
/>
1238+
</g>
1239+
</template>
11791240
</g>
1180-
<g v-if="FINAL_CONFIG.style.layout.marginalBars.showLines">
1241+
<g v-if="FINAL_CONFIG.style.layout.marginalBars.showLines" style="pointer-events: none;">
11811242
<template v-for="line in marginalLines">
11821243
<path
11831244
data-cy="marginal-line-x-wrapper"
@@ -1223,6 +1284,57 @@ defineExpose({
12231284
</g>
12241285
</g>
12251286
1287+
<!-- GIFT WRAP -->
1288+
<g v-if="FINAL_CONFIG.style.layout.plots.giftWrap.show">
1289+
<g v-for="(ds, i) in drawableDataset">
1290+
<polygon
1291+
v-if="ds.plots.length > 2"
1292+
:points="giftWrap({series: ds.plots})"
1293+
:fill="setOpacity(ds.color, FINAL_CONFIG.style.layout.plots.giftWrap.fillOpacity * 100)"
1294+
:stroke-width="FINAL_CONFIG.style.layout.plots.giftWrap.strokeWidth"
1295+
:stroke-dasharray="FINAL_CONFIG.style.layout.plots.giftWrap.strokeDasharray"
1296+
:stroke="ds.color"
1297+
stroke-linejoin="round"
1298+
stroke-linecap="round"
1299+
/>
1300+
</g>
1301+
</g>
1302+
1303+
<!-- PLOTS -->
1304+
<g v-for="(ds, i) in drawableDataset">
1305+
<g v-if="!ds.shape || ds.shape === 'circle'">
1306+
<circle
1307+
v-for="(plot, j) in ds.plots"
1308+
data-cy="atom-shape"
1309+
:cx="plot.x"
1310+
:cy="plot.y"
1311+
:r="selectedPlotId && selectedPlotId === plot.id ? plot.weight * 2 : plot.weight"
1312+
:fill="setOpacity(ds.color, FINAL_CONFIG.style.layout.plots.opacity * 100)"
1313+
:stroke="FINAL_CONFIG.style.layout.plots.stroke"
1314+
:stroke-width="FINAL_CONFIG.style.layout.plots.strokeWidth"
1315+
:style="`opacity:${selectedPlotId && selectedPlotId === plot.id ? 1 : FINAL_CONFIG.style.layout.plots.significance.useDistanceOpacity ? (1 - (Math.abs(plot.deviation) / maxDeviation)) : FINAL_CONFIG.style.layout.plots.significance.show && Math.abs(plot.deviation) > FINAL_CONFIG.style.layout.plots.significance.deviationThreshold ? FINAL_CONFIG.style.layout.plots.significance.opacity : 1}`"
1316+
@mouseover="onTrapEnter(plot, i)"
1317+
@mouseleave="onTrapLeave(plot, i)"
1318+
@click="onTrapClick(plot, i)"
1319+
/>
1320+
</g>
1321+
<g v-else>
1322+
<Shape
1323+
v-for="(plot, j) in ds.plots"
1324+
:plot="{x: plot.x, y: plot.y }"
1325+
:radius="selectedPlotId && selectedPlotId === plot.id ? plot.weight * 2 : plot.weight"
1326+
:shape="ds.shape"
1327+
:color="setOpacity(ds.color, FINAL_CONFIG.style.layout.plots.opacity * 100)"
1328+
:stroke="FINAL_CONFIG.style.layout.plots.stroke"
1329+
:strokeWidth="FINAL_CONFIG.style.layout.plots.strokeWidth"
1330+
:style="`opacity:${selectedPlotId && selectedPlotId === plot.id ? 1 : FINAL_CONFIG.style.layout.plots.significance.useDistanceOpacity ? (1 - (Math.abs(plot.deviation) / maxDeviation)) : FINAL_CONFIG.style.layout.plots.significance.show && Math.abs(plot.deviation) > FINAL_CONFIG.style.layout.plots.significance.deviationThreshold ? FINAL_CONFIG.style.layout.plots.significance.opacity : 1}`"
1331+
@mouseover="onTrapEnter(plot, i)"
1332+
@mouseleave="onTrapLeave(plot, i)"
1333+
@click="onTrapClick(plot, i)"
1334+
/>
1335+
</g>
1336+
</g>
1337+
12261338
<!-- SELECTORS -->
12271339
<g v-if="selectedPlot && FINAL_CONFIG.style.layout.plots.selectors.show" style="pointer-events: none !important;">
12281340
<line

src/useConfig.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2107,7 +2107,16 @@ export function useConfig() {
21072107
borderRadius: 2,
21082108
useGradient: true,
21092109
showLines: false,
2110-
linesStrokeWidth: 1
2110+
linesStrokeWidth: 1,
2111+
highlighter: {
2112+
show: true,
2113+
opacity: 0.1,
2114+
color: COLOR_BLACK,
2115+
stroke: COLOR_BLACK,
2116+
strokeWidth: 0.5,
2117+
strokeDasharray: 2,
2118+
highlightBothAxes: false,
2119+
}
21112120
},
21122121
plots: {
21132122
radius: 2,

types/vue-data-ui.d.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2874,6 +2874,15 @@ declare module "vue-data-ui" {
28742874
useGradient?: boolean;
28752875
showLines?: boolean;
28762876
linesStrokeWidth?: number;
2877+
highlighter?: {
2878+
show?: boolean;
2879+
opacity?: number;
2880+
color?: string;
2881+
stroke?: string;
2882+
strokeWidth?: number;
2883+
strokeDasharray?: number;
2884+
highlightBothAxes?: boolean;
2885+
}
28772886
};
28782887
correlation?: {
28792888
show?: boolean;

0 commit comments

Comments
 (0)