@@ -4,7 +4,7 @@ import createPubSub from 'pub-sub-es';
44import withRaf from 'with-raf' ;
55import { mat4 , vec4 } from 'gl-matrix' ;
66import createLine from 'regl-line' ;
7- import { identity , unionIntegers } from '@flekschas/utils' ;
7+ import { identity , rangeMap , unionIntegers } from '@flekschas/utils' ;
88
99import createLassoManager from './lasso-manager' ;
1010
@@ -101,6 +101,7 @@ import {
101101 getBBox ,
102102 isConditionalArray ,
103103 isPositiveNumber ,
104+ isStrictlyPositiveNumber ,
104105 isMultipleColors ,
105106 isPointInPolygon ,
106107 isString ,
@@ -126,7 +127,11 @@ const checkDeprecations = (properties) => {
126127 } ) ;
127128} ;
128129
129- const getEncodingType = ( type , defaultValue ) => {
130+ const getEncodingType = (
131+ type ,
132+ defaultValue ,
133+ { allowSegment = false } = false
134+ ) => {
130135 switch ( type ) {
131136 case 'category' :
132137 case 'value1' :
@@ -142,6 +147,9 @@ const getEncodingType = (type, defaultValue) => {
142147 case 'w' :
143148 return 'valueW' ; // W refers to the 4th component of the RGBA value
144149
150+ case 'segment' :
151+ return allowSegment ? 'segment' : defaultValue ;
152+
145153 default :
146154 return defaultValue ;
147155 }
@@ -351,13 +359,25 @@ const createScatterplot = (initialProperties = {}) => {
351359 : [ pointConnectionSize ] ;
352360 }
353361
354- colorBy = getEncodingType ( colorBy ) ;
355- opacityBy = getEncodingType ( opacityBy ) ;
356- sizeBy = getEncodingType ( sizeBy ) ;
362+ colorBy = getEncodingType ( colorBy , DEFAULT_COLOR_BY ) ;
363+ opacityBy = getEncodingType ( opacityBy , DEFAULT_OPACITY_BY ) ;
364+ sizeBy = getEncodingType ( sizeBy , DEFAULT_SIZE_BY ) ;
357365
358- pointConnectionColorBy = getEncodingType ( pointConnectionColorBy ) ;
359- pointConnectionOpacityBy = getEncodingType ( pointConnectionOpacityBy ) ;
360- pointConnectionSizeBy = getEncodingType ( pointConnectionSizeBy ) ;
366+ pointConnectionColorBy = getEncodingType (
367+ pointConnectionColorBy ,
368+ DEFAULT_POINT_CONNECTION_COLOR_BY ,
369+ { allowSegment : true }
370+ ) ;
371+ pointConnectionOpacityBy = getEncodingType (
372+ pointConnectionOpacityBy ,
373+ DEFAULT_POINT_CONNECTION_OPACITY_BY ,
374+ { allowSegment : true }
375+ ) ;
376+ pointConnectionSizeBy = getEncodingType (
377+ pointConnectionSizeBy ,
378+ DEFAULT_POINT_CONNECTION_SIZE_BY ,
379+ { allowSegment : true }
380+ ) ;
361381
362382 let stateTex ; // Stores the point texture holding x, y, category, and value
363383 let prevStateTex ; // Stores the previous point texture. Used for transitions
@@ -959,7 +979,7 @@ const createScatterplot = (initialProperties = {}) => {
959979 if ( isConditionalArray ( newPointSize , isPositiveNumber , { minLength : 1 } ) )
960980 pointSize = [ ...newPointSize ] ;
961981
962- if ( isPositiveNumber ( + newPointSize ) ) pointSize = [ + newPointSize ] ;
982+ if ( isStrictlyPositiveNumber ( + newPointSize ) ) pointSize = [ + newPointSize ] ;
963983
964984 encodingTex = createEncodingTexture ( ) ;
965985 computePointSizeMouseDetection ( ) ;
@@ -989,7 +1009,7 @@ const createScatterplot = (initialProperties = {}) => {
9891009 if ( isConditionalArray ( newOpacity , isPositiveNumber , { minLength : 1 } ) )
9901010 opacity = [ ...newOpacity ] ;
9911011
992- if ( isPositiveNumber ( + newOpacity ) ) opacity = [ + newOpacity ] ;
1012+ if ( isStrictlyPositiveNumber ( + newOpacity ) ) opacity = [ + newOpacity ] ;
9931013
9941014 encodingTex = createEncodingTexture ( ) ;
9951015 } ;
@@ -1007,14 +1027,14 @@ const createScatterplot = (initialProperties = {}) => {
10071027 }
10081028 } ;
10091029
1010- const getEncodingValueToIdx = ( type , rangeMap ) => {
1030+ const getEncodingValueToIdx = ( type , rangeValues ) => {
10111031 switch ( type ) {
10121032 default :
10131033 case 'categorical' :
10141034 return identity ;
10151035
10161036 case 'continuous' :
1017- return ( value ) => Math . round ( value * ( rangeMap . length - 1 ) ) ;
1037+ return ( value ) => Math . round ( value * ( rangeValues . length - 1 ) ) ;
10181038 }
10191039 } ;
10201040
@@ -1030,19 +1050,22 @@ const createScatterplot = (initialProperties = {}) => {
10301050 const setPointConnectionColorBy = ( type ) => {
10311051 pointConnectionColorBy = getEncodingType (
10321052 type ,
1033- DEFAULT_POINT_CONNECTION_COLOR_BY
1053+ DEFAULT_POINT_CONNECTION_COLOR_BY ,
1054+ { allowSegment : true }
10341055 ) ;
10351056 } ;
10361057 const setPointConnectionOpacityBy = ( type ) => {
10371058 pointConnectionOpacityBy = getEncodingType (
10381059 type ,
1039- DEFAULT_POINT_CONNECTION_OPACITY_BY
1060+ DEFAULT_POINT_CONNECTION_OPACITY_BY ,
1061+ { allowSegment : true }
10401062 ) ;
10411063 } ;
10421064 const setPointConnectionSizeBy = ( type ) => {
10431065 pointConnectionSizeBy = getEncodingType (
10441066 type ,
1045- DEFAULT_POINT_CONNECTION_SIZE_BY
1067+ DEFAULT_POINT_CONNECTION_SIZE_BY ,
1068+ { allowSegment : true }
10461069 ) ;
10471070 } ;
10481071
@@ -1437,10 +1460,45 @@ const createScatterplot = (initialProperties = {}) => {
14371460 isInit = true ;
14381461 } ;
14391462
1440- const getPointConnectionColorIndices = ( ) => {
1463+ const getPointConnectionColorIndices = ( curvePoints ) => {
14411464 const colorEncoding =
14421465 pointConnectionColorBy === 'inherit' ? colorBy : pointConnectionColorBy ;
14431466
1467+ if ( colorEncoding === 'segment' ) {
1468+ const maxColorIdx = pointConnectionColor . length - 1 ;
1469+ if ( maxColorIdx < 1 ) return [ ] ;
1470+ return curvePoints . reduce ( ( colorIndices , curve , index ) => {
1471+ let totalLength = 0 ;
1472+ const segLengths = [ ] ;
1473+ // Compute the total length of the line
1474+ for ( let i = 2 ; i < curve . length ; i += 2 ) {
1475+ const segLength = Math . sqrt (
1476+ ( curve [ i - 2 ] - curve [ i ] ) ** 2 + ( curve [ i - 1 ] - curve [ i + 1 ] ) ** 2
1477+ ) ;
1478+ segLengths . push ( segLength ) ;
1479+ totalLength += segLength ;
1480+ }
1481+ colorIndices [ index ] = [ 0 ] ;
1482+ let cumLength = 0 ;
1483+ // Assign the color index based on the cumulative length
1484+ for ( let i = 0 ; i < curve . length / 2 - 1 ; i ++ ) {
1485+ cumLength += segLengths [ i ] ;
1486+ // The `4` comes from the fact that we have 4 color states:
1487+ // normal, active, hover, and background
1488+ colorIndices [ index ] . push (
1489+ Math . floor ( ( cumLength / totalLength ) * maxColorIdx ) * 4
1490+ ) ;
1491+ }
1492+ // The `4` comes from the fact that we have 4 color states:
1493+ // normal, active, hover, and background
1494+ // colorIndices[index] = rangeMap(
1495+ // curve.length,
1496+ // (i) => Math.floor((i / (curve.length - 1)) * maxColorIdx) * 4
1497+ // );
1498+ return colorIndices ;
1499+ } , [ ] ) ;
1500+ }
1501+
14441502 if ( colorEncoding ) {
14451503 const encodingIdx = getEncodingIdx ( colorEncoding ) ;
14461504 const encodingValueToIdx = getEncodingValueToIdx (
@@ -1468,6 +1526,25 @@ const createScatterplot = (initialProperties = {}) => {
14681526 ? opacityBy
14691527 : pointConnectionOpacityBy ;
14701528
1529+ if ( opacityEncoding === 'segment' ) {
1530+ const maxOpacityIdx = pointConnectionOpacity . length - 1 ;
1531+ if ( maxOpacityIdx < 1 ) return [ ] ;
1532+ return pointConnectionMap . reduce (
1533+ // eslint-disable-next-line no-unused-vars
1534+ ( opacities , [ index , referencePoint , length ] ) => {
1535+ opacities [ index ] = rangeMap (
1536+ length ,
1537+ ( i ) =>
1538+ pointConnectionOpacity [
1539+ Math . floor ( ( i / ( length - 1 ) ) * maxOpacityIdx )
1540+ ]
1541+ ) ;
1542+ return opacities ;
1543+ } ,
1544+ [ ]
1545+ ) ;
1546+ }
1547+
14711548 if ( opacityEncoding ) {
14721549 const encodingIdx = getEncodingIdx ( opacityEncoding ) ;
14731550 const encodingRangeMap =
@@ -1492,6 +1569,23 @@ const createScatterplot = (initialProperties = {}) => {
14921569 const sizeEncoding =
14931570 pointConnectionSizeBy === 'inherit' ? sizeBy : pointConnectionSizeBy ;
14941571
1572+ if ( sizeEncoding === 'segment' ) {
1573+ const maxSizeIdx = pointConnectionSize . length - 1 ;
1574+ if ( maxSizeIdx < 1 ) return [ ] ;
1575+ return pointConnectionMap . reduce (
1576+ // eslint-disable-next-line no-unused-vars
1577+ ( widths , [ index , referencePoint , length ] ) => {
1578+ widths [ index ] = rangeMap (
1579+ length ,
1580+ ( i ) =>
1581+ pointConnectionSize [ Math . floor ( ( i / ( length - 1 ) ) * maxSizeIdx ) ]
1582+ ) ;
1583+ return widths ;
1584+ } ,
1585+ [ ]
1586+ ) ;
1587+ }
1588+
14951589 if ( sizeEncoding ) {
14961590 const encodingIdx = getEncodingIdx ( sizeEncoding ) ;
14971591 const encodingRangeMap =
@@ -1519,6 +1613,8 @@ const createScatterplot = (initialProperties = {}) => {
15191613 index ,
15201614 curvePoints [ id ] . reference ,
15211615 curvePoints [ id ] . length / 2 ,
1616+ // Used for offsetting in the buffer manipulations on
1617+ // hovering and selecting
15221618 cumLinePoints ,
15231619 ] ;
15241620 cumLinePoints += curvePoints [ id ] . length / 2 ;
@@ -1537,10 +1633,11 @@ const createScatterplot = (initialProperties = {}) => {
15371633 tolerance : pointConnectionTolerance ,
15381634 } ) . then ( ( curvePoints ) => {
15391635 setPointConnectionMap ( curvePoints ) ;
1540- pointConnections . setPoints ( Object . values ( curvePoints ) , {
1541- colorIndices : getPointConnectionColorIndices ( ) ,
1542- opacities : getPointConnectionOpacities ( ) ,
1543- widths : getPointConnectionWidths ( ) ,
1636+ const curvePointValues = Object . values ( curvePoints ) ;
1637+ pointConnections . setPoints ( curvePointValues , {
1638+ colorIndices : getPointConnectionColorIndices ( curvePointValues ) ,
1639+ opacities : getPointConnectionOpacities ( curvePointValues ) ,
1640+ widths : getPointConnectionWidths ( curvePointValues ) ,
15441641 } ) ;
15451642 computingPointConnectionCurves = false ;
15461643 resolve ( ) ;
@@ -1946,10 +2043,13 @@ const createScatterplot = (initialProperties = {}) => {
19462043 if ( isConditionalArray ( newOpacity , isPositiveNumber , { minLength : 1 } ) )
19472044 pointConnectionOpacity = [ ...newOpacity ] ;
19482045
1949- if ( isPositiveNumber ( + newOpacity ) ) pointConnectionOpacity = [ + newOpacity ] ;
2046+ if ( isStrictlyPositiveNumber ( + newOpacity ) )
2047+ pointConnectionOpacity = [ + newOpacity ] ;
19502048
19512049 pointConnectionColor = pointConnectionColor . map ( ( color ) => {
1952- color [ 3 ] = pointConnectionOpacity [ 0 ] ;
2050+ color [ 3 ] = ! Number . isNaN ( + pointConnectionOpacity [ 0 ] )
2051+ ? + pointConnectionOpacity [ 0 ]
2052+ : color [ 3 ] ;
19532053 return color ;
19542054 } ) ;
19552055
@@ -1969,7 +2069,7 @@ const createScatterplot = (initialProperties = {}) => {
19692069 )
19702070 pointConnectionSize = [ ...newPointConnectionSize ] ;
19712071
1972- if ( isPositiveNumber ( + newPointConnectionSize ) )
2072+ if ( isStrictlyPositiveNumber ( + newPointConnectionSize ) )
19732073 pointConnectionSize = [ + newPointConnectionSize ] ;
19742074
19752075 updatePointConnectionStyle ( ) ;
0 commit comments