From eb0b60f9f06c25e6c75f2f8ff3f2740f4124344c Mon Sep 17 00:00:00 2001 From: Michael Behrisch <m.behrisch@uu.nl> Date: Thu, 6 Jul 2023 17:28:15 +0200 Subject: [PATCH] fix: :sparkles: adds multiple attributes per node to dotplotvis Once more than one attribute will be pulled into the querypanel the dotplot vis needs to show also multiple (coordinated) dotplots --- .../lib/vis/dotplotsvis/dotplotsvis.tsx | 521 ++++++++++++------ 1 file changed, 358 insertions(+), 163 deletions(-) diff --git a/libs/shared/lib/vis/dotplotsvis/dotplotsvis.tsx b/libs/shared/lib/vis/dotplotsvis/dotplotsvis.tsx index 6a2a1c2d3..cdd45d4dc 100644 --- a/libs/shared/lib/vis/dotplotsvis/dotplotsvis.tsx +++ b/libs/shared/lib/vis/dotplotsvis/dotplotsvis.tsx @@ -15,182 +15,374 @@ export const DotPlotsVis = React.memo((props: DotPlotsVisProps) => { const dispatch = useAppDispatch(); const theme = useTheme(); - let [oneRowPoints, setOneRowPoints] = useState([]); - let [minMax, setMinMax] = useState([]); - let [attributeName, setAttributeName] = useState(''); + const [dotPlotSpec, setDotPlotSpec] = useState<any>([]); + + // let [oneRowPoints, setOneRowPoints] = useState<number[]>([]); + // let [minMax, setMinMax] = useState<number[]>([]); + // let [attributeName, setAttributeName] = useState<string>(''); // let [spec, setSpec] = useState({}); - let spec = { - $schema: 'https://vega.github.io/schema/vega/v5.json', - description: - 'A dot plot example depicting the distribution of animal sleep times.', - width: 500, - padding: 5, - autosize: 'pad', - - signals: [ - { - name: 'step', - value: (minMax[1] - minMax[0]) / 40, // default 40 bins - bind: { - input: 'range', - min: (minMax[1] - minMax[0]) / 100, // 4 bins - 100 bins - max: (minMax[1] - minMax[0]) / 10, // max 100 bins - step: 1, - }, - }, - // { - // name: 'offset', - // value: 'zero', - // bind: { input: 'radio', options: ['zero', 'center'] }, - // }, - // { - // name: 'smooth', - // value: true, - // bind: { input: 'checkbox' }, - // }, - { name: 'size', update: "scale('x', step) - scale('x', 0)" }, - { name: 'area', update: 'size * size' }, - { name: 'ddh', update: '(span(ddext) + 1) * size' }, - { name: 'hdh', update: '(span(hdext) + 1) * size' }, - { name: 'height', update: 'max(ddh, hdh)' }, - ], - - data: [ - { - name: 'points', - values: oneRowPoints, - transform: [ - { - type: 'dotbin', - field: 'data', - // smooth: { signal: 'smooth' }, - smooth: 'true', - step: { signal: 'step' }, - }, - { - type: 'stack', - groupby: ['bin'], - // offset: { signal: 'offset' }, - offset: 'zero', - as: ['d0', 'd1'], - }, - { - type: 'extent', - field: 'd1', - signal: 'ddext', - }, - { - type: 'extent', - field: 'data', - signal: 'ext', - }, - { - type: 'bin', - field: 'data', - step: { signal: 'step' }, - extent: { signal: 'ext' }, - }, - { - type: 'stack', - // offset: { signal: 'offset' }, - offset: 'zero', + // let spec = { + // $schema: 'https://vega.github.io/schema/vega/v5.json', + // description: + // 'A dot plot example depicting the distribution of animal sleep times.', + // width: 500, + // padding: 5, + // autosize: 'pad', - groupby: ['bin0'], - }, - { - type: 'extent', - field: 'y0', - signal: 'hdext', - }, - ], - }, - ], - - scales: [ - { - name: 'x', - domain: [minMax[0], minMax[1]], - range: 'width', - }, - { - name: 'ddy', - domain: { signal: '[0, ddh / size]' }, - range: { signal: '[height, height - ddh]' }, - }, - { - name: 'hdy', - domain: { signal: '[0, hdh / size]' }, - range: { signal: '[height, height - hdh]' }, - }, - ], - - marks: [ - { - type: 'group', - encode: { - update: { - width: { signal: 'width' }, - height: { signal: 'height' }, - }, - }, - axes: [ - { - scale: 'x', - orient: 'bottom', - tickCount: 5, - title: 'Histogram (' + attributeName + ')', - }, - ], - marks: [ - { - type: 'symbol', - from: { data: 'points' }, - encode: { - update: { - x: { scale: 'x', signal: '(datum.bin0 + datum.bin1) / 2' }, - y: { scale: 'hdy', signal: 'datum.y0 + 0.5' }, - size: { signal: 'area' }, - // fill: { value: 'steelblue' }, - fill: { value: theme.palette.primary.light }, - }, - enter: { - tooltip: { - signal: - "{'Value range': format(datum.bin0, '0.1f') + ' - ' + format(datum.bin1, '0.1f')}", - }, - }, - hover: { fill: { value: theme.palette.primary.dark } }, - }, - }, - ], - }, - ], - }; + // signals: [ + // { + // name: 'step', + // value: (minMax[1] - minMax[0]) / 40, // default 40 bins + // bind: { + // input: 'range', + // min: (minMax[1] - minMax[0]) / 100, // 4 bins - 100 bins + // max: (minMax[1] - minMax[0]) / 10, // max 100 bins + // step: 1, + // }, + // }, + // // { + // // name: 'offset', + // // value: 'zero', + // // bind: { input: 'radio', options: ['zero', 'center'] }, + // // }, + // // { + // // name: 'smooth', + // // value: true, + // // bind: { input: 'checkbox' }, + // // }, + // { name: 'size', update: "scale('x', step) - scale('x', 0)" }, + // { name: 'area', update: 'size * size' }, + // { name: 'ddh', update: '(span(ddext) + 1) * size' }, + // { name: 'hdh', update: '(span(hdext) + 1) * size' }, + // { name: 'height', update: 'max(ddh, hdh)' }, + // ], + + // data: [ + // { + // name: 'points', + // values: oneRowPoints, + // transform: [ + // { + // type: 'dotbin', + // field: 'data', + // // smooth: { signal: 'smooth' }, + // smooth: 'true', + // step: { signal: 'step' }, + // }, + // { + // type: 'stack', + // groupby: ['bin'], + // // offset: { signal: 'offset' }, + // offset: 'zero', + // as: ['d0', 'd1'], + // }, + // { + // type: 'extent', + // field: 'd1', + // signal: 'ddext', + // }, + // { + // type: 'extent', + // field: 'data', + // signal: 'ext', + // }, + // { + // type: 'bin', + // field: 'data', + // step: { signal: 'step' }, + // extent: { signal: 'ext' }, + // }, + // { + // type: 'stack', + // // offset: { signal: 'offset' }, + // offset: 'zero', + + // groupby: ['bin0'], + // }, + // { + // type: 'extent', + // field: 'y0', + // signal: 'hdext', + // }, + // ], + // }, + // ], + + // scales: [ + // { + // name: 'x', + // domain: [minMax[0], minMax[1]], + // range: 'width', + // }, + // { + // name: 'ddy', + // domain: { signal: '[0, ddh / size]' }, + // range: { signal: '[height, height - ddh]' }, + // }, + // { + // name: 'hdy', + // domain: { signal: '[0, hdh / size]' }, + // range: { signal: '[height, height - hdh]' }, + // }, + // ], + + // marks: [ + // { + // type: 'group', + // encode: { + // update: { + // width: { signal: 'width' }, + // height: { signal: 'height' }, + // }, + // }, + // axes: [ + // { + // scale: 'x', + // orient: 'bottom', + // tickCount: 5, + // tickRound: true, + // tickOpacity: 0.5, + // tickCap: 'round', + // tickExtra: true, + // // tickWidth: 2, + + // // TODO: adapt the tick according to theme/new design https://vega.github.io/vega/docs/axes/ + // labelColor: theme.palette.text.secondary, + // // labelFont: theme.typography.body2.fontFamily, + // // labelFontSize: theme.typography.body2.fontSize, + // // labelFontWeight: theme.typography.body2.fontWeight, + // // labelFontStyle: theme.typography.body2.fontStyle, + // title: 'Histogram (' + attributeName + ')', + // }, + // ], + // marks: [ + // { + // type: 'symbol', + // from: { data: 'points' }, + // encode: { + // update: { + // x: { scale: 'x', signal: '(datum.bin0 + datum.bin1) / 2' }, + // y: { scale: 'hdy', signal: 'datum.y0 + 0.5' }, + // size: { signal: 'area' }, + // // fill: { value: 'steelblue' }, + // fill: { value: theme.palette.primary.light }, + // }, + // enter: { + // tooltip: { + // signal: + // "{'Value range': format(datum.bin0, '0.1f') + ' - ' + format(datum.bin1, '0.1f')}", + // }, + // }, + // hover: { fill: { value: theme.palette.primary.dark } }, + // }, + // }, + // ], + // }, + // ], + // }; useEffect(() => { - console.log('update dotplotvis useEffect', graphQueryResult); - - if (graphQueryResult) { - const rowPoints: number[] = []; - for (let i = 0; i < graphQueryResult.nodes.length; i++) { - // const attributes = graphQueryResult.nodes[i].attributes - const value = graphQueryResult.nodes[i].attributes.attribute0; - rowPoints.push(value); - } + console.log( + 'update dotplotvis useEffect', + graphQueryResult, + graphQueryResult.nodes[0].attributes + ); + + if (graphQueryResult && graphQueryResult.nodes.length > 0) { + const node = graphQueryResult.nodes[0]; + const attributes = node.attributes; + + for (const [key, value] of Object.entries(attributes)) { + // console.log(`${key}: ${value}`); - setMinMax([Math.min(...rowPoints), Math.max(...rowPoints)]); - setOneRowPoints(rowPoints); - setAttributeName('attribute0'); //fake for simple version. needs to be determined from query result + if (typeof value == 'number') { + // console.log('number', value, key); + const rowPoints: number[] = []; + for (let i = 0; i < graphQueryResult.nodes.length; i++) { + const value = graphQueryResult.nodes[i].attributes[key]; + rowPoints.push(value); + } + const dotPlotSpec = generateNewDotPlotSpec(rowPoints, key); + setDotPlotSpec((oldArray: any) => [...oldArray, dotPlotSpec]); + } else { + console.log('attribute is not number: TBD', key); + } + } } }, [graphQueryResult]); useEffect(() => { return () => { + setDotPlotSpec([]); console.log('unloaded dotplotvis'); }; }, []); + function generateNewDotPlotSpec(rowPoints: number[], attributeName: string) { + const minMax = [Math.min(...rowPoints), Math.max(...rowPoints)]; + + return { + $schema: 'https://vega.github.io/schema/vega/v5.json', + description: + 'A dot plot example depicting the distribution of animal sleep times.', + width: 500, + padding: 5, + autosize: 'pad', + + signals: [ + { + name: 'step', + value: (minMax[1] - minMax[0]) / 40, // default 40 bins + bind: { + input: 'range', + min: (minMax[1] - minMax[0]) / 100, // 4 bins - 100 bins + max: (minMax[1] - minMax[0]) / 10, // max 100 bins + step: 1, + }, + }, + // { + // name: 'offset', + // value: 'zero', + // bind: { input: 'radio', options: ['zero', 'center'] }, + // }, + // { + // name: 'smooth', + // value: true, + // bind: { input: 'checkbox' }, + // }, + { name: 'size', update: "scale('x', step) - scale('x', 0)" }, + { name: 'area', update: 'size * size' }, + { name: 'ddh', update: '(span(ddext) + 1) * size' }, + { name: 'hdh', update: '(span(hdext) + 1) * size' }, + { name: 'height', update: 'max(ddh, hdh)' }, + ], + + data: [ + { + name: 'points', + values: rowPoints, + transform: [ + { + type: 'dotbin', + field: 'data', + // smooth: { signal: 'smooth' }, + smooth: 'true', + step: { signal: 'step' }, + }, + { + type: 'stack', + groupby: ['bin'], + // offset: { signal: 'offset' }, + offset: 'zero', + as: ['d0', 'd1'], + }, + { + type: 'extent', + field: 'd1', + signal: 'ddext', + }, + { + type: 'extent', + field: 'data', + signal: 'ext', + }, + { + type: 'bin', + field: 'data', + step: { signal: 'step' }, + extent: { signal: 'ext' }, + }, + { + type: 'stack', + // offset: { signal: 'offset' }, + offset: 'zero', + + groupby: ['bin0'], + }, + { + type: 'extent', + field: 'y0', + signal: 'hdext', + }, + ], + }, + ], + + scales: [ + { + name: 'x', + domain: [minMax[0], minMax[1]], + range: 'width', + }, + { + name: 'ddy', + domain: { signal: '[0, ddh / size]' }, + range: { signal: '[height, height - ddh]' }, + }, + { + name: 'hdy', + domain: { signal: '[0, hdh / size]' }, + range: { signal: '[height, height - hdh]' }, + }, + ], + + marks: [ + { + type: 'group', + encode: { + update: { + width: { signal: 'width' }, + height: { signal: 'height' }, + }, + }, + axes: [ + { + scale: 'x', + orient: 'bottom', + tickCount: 5, + tickRound: true, + tickOpacity: 0.5, + tickCap: 'round', + tickExtra: true, + // tickWidth: 2, + + // TODO: adapt the tick according to theme/new design https://vega.github.io/vega/docs/axes/ + labelColor: theme.palette.text.secondary, + // labelFont: theme.typography.body2.fontFamily, + // labelFontSize: theme.typography.body2.fontSize, + // labelFontWeight: theme.typography.body2.fontWeight, + // labelFontStyle: theme.typography.body2.fontStyle, + title: 'Histogram (' + attributeName + ')', + }, + ], + marks: [ + { + type: 'symbol', + from: { data: 'points' }, + encode: { + update: { + x: { scale: 'x', signal: '(datum.bin0 + datum.bin1) / 2' }, + y: { scale: 'hdy', signal: 'datum.y0 + 0.5' }, + size: { signal: 'area' }, + // fill: { value: 'steelblue' }, + fill: { value: theme.palette.primary.light }, + }, + enter: { + tooltip: { + signal: + "{'Value range': format(datum.bin0, '0.1f') + ' - ' + format(datum.bin1, '0.1f')}", + }, + }, + hover: { fill: { value: theme.palette.primary.dark } }, + }, + }, + ], + }, + ], + }; + } + const loading = props.loading; return ( @@ -204,7 +396,10 @@ export const DotPlotsVis = React.memo((props: DotPlotsVisProps) => { }} > <h1>DotPlotsVis</h1> - <Vega spec={spec} /> + {/* <Vega spec={spec} /> */} + {dotPlotSpec.map((spec: any, i: number) => ( + <Vega key={i} spec={spec} /> + ))} <div id="vis"></div> </div> </div> @@ -230,4 +425,4 @@ export const DotPlotsVis = React.memo((props: DotPlotsVisProps) => { ); }); -DotPlotsVis.displayName = 'RawJSONVis'; +DotPlotsVis.displayName = 'DotPlotVis'; -- GitLab