d3blocks 2.3.0__tar.gz → 2.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {d3blocks-2.3.0/d3blocks.egg-info → d3blocks-2.3.1}/PKG-INFO +1 -1
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/__init__.py +1 -1
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/d3blocks.py +12 -9
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/movingbubbles/Movingbubbles.py +76 -18
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/movingbubbles/d3js/movingbubbles.html.j2 +68 -14
- d3blocks-2.3.1/d3blocks/tests/test_movingbubbles_color.py +93 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1/d3blocks.egg-info}/PKG-INFO +1 -1
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks.egg-info/SOURCES.txt +1 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/LICENSE +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/MANIFEST.in +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/README.md +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/_shared/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/_shared/d3js/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/_shared/d3js/d3.v7.min.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/_shared/d3js/theme.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/_shared/d3js/walkthrough.css +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/_shared/d3js/walkthrough.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/chord/Chord.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/chord/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/chord/d3js/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/chord/d3js/chord.css +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/chord/d3js/chord.html.j2 +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/chord/d3js/chord.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/circlepacking/Circlepacking.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/circlepacking/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/circlepacking/d3js/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/circlepacking/d3js/circlepacking.css +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/circlepacking/d3js/circlepacking.html.j2 +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/circlepacking/d3js/d3.v6.min.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/circlepacking/d3js/lodash.min.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/elasticgraph/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/elasticgraph/d3js/.gitkeep +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/elasticgraph/d3js/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/elasticgraph/d3js/d3.v2.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/elasticgraph/d3js/elasticgraph.css +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/elasticgraph/d3js/elasticgraph.html.j2 +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/elasticgraph/d3js/elasticgraph.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/elasticgraph/elasticgraph.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/erd/Erd.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/erd/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/erd/d3js/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/erd/d3js/d3.erd.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/erd/d3js/dagre.LICENSE +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/erd/d3js/dagre.min.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/erd/d3js/erd.css +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/erd/d3js/erd.html.j2 +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/examples.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/examples_docs.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/flowgraph/Flowgraph.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/flowgraph/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/flowgraph/d3js/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/flowgraph/d3js/d3.flowgraph.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/flowgraph/d3js/d3.v7.min.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/flowgraph/d3js/dagre.LICENSE +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/flowgraph/d3js/dagre.min.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/flowgraph/d3js/elk.LICENSE.md +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/flowgraph/d3js/elk.bundled.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/flowgraph/d3js/flowgraph.css +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/flowgraph/d3js/flowgraph.html.j2 +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/heatmap/Heatmap.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/heatmap/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/heatmap/d3js/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/heatmap/d3js/d3.scale.chromatic.v1.min.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/heatmap/d3js/d3.v4.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/heatmap/d3js/heatmap.css +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/heatmap/d3js/heatmap.html.j2 +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/heatmap/d3js/matrix.html.j2 +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/imageslider/Imageslider.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/imageslider/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/imageslider/d3js/.keep +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/imageslider/d3js/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/imageslider/d3js/imageslider.css +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/imageslider/d3js/imageslider.html.j2 +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/imageslider/d3js/imageslider.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/imageslider/d3js/jquery-2.1.1.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/imageslider/d3js/modernizr.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/imageslider/d3js/reset.css +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/logo.txt +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/maps/Maps.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/maps/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/maps/d3js/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/maps/d3js/d3.v6.min.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/maps/d3js/geojson.zip +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/maps/d3js/maps.css +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/maps/d3js/maps.html.j2 +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/maps/d3js/world.geojson +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/matrix/Matrix.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/matrix/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/matrix/d3js/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/matrix/d3js/d3.scale.chromatic.v1.min.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/matrix/d3js/d3.v4.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/matrix/d3js/matrix.html.j2 +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/movingbubbles/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/movingbubbles/d3js/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/movingbubbles/d3js/d3-3-5-5.min.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/movingbubbles/d3js/movingbubbles.css +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/particles/Particles.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/particles/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/particles/d3js/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/particles/d3js/d3-scale-chromatic.v1.min.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/particles/d3js/d3.v4.min.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/particles/d3js/particles.html.j2 +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/radialgraph/Radialgraph.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/radialgraph/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/radialgraph/d3js/__init__ .py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/radialgraph/d3js/d3.radialgraph.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/radialgraph/d3js/d3.v7.min.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/radialgraph/d3js/radialgraph.css +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/radialgraph/d3js/radialgraph.html.j2 +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/sankey/Sankey.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/sankey/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/sankey/d3js/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/sankey/d3js/sankey.css +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/sankey/d3js/sankey.html.j2 +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/sankey/d3js/sankey.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/scatter/Scatter.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/scatter/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/scatter/d3js/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/scatter/d3js/associations.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/scatter/d3js/d3.v4.min.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/scatter/d3js/scatter.css +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/scatter/d3js/scatter.html.j2 +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/sequence/Sequence.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/sequence/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/sequence/d3js/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/sequence/d3js/d3.sequence.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/sequence/d3js/sequence.css +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/sequence/d3js/sequence.html.j2 +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/tests/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/tests/test_all_examples.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/tests/test_chord_margins.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/tests/test_d3blocks.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/tests/test_erd.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/tests/test_flowgraph.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/tests/test_medium_charts.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/tests/test_movingbubbles_timedelta.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/tests/test_radialgraph.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/tests/test_sequence.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/timeseries/Timeseries.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/timeseries/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/timeseries/d3js/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/timeseries/d3js/d3.v3.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/timeseries/d3js/timeseries.css +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/timeseries/d3js/timeseries.html.j2 +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/timeseries/d3js/timeseries.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/tree/Tree.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/tree/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/tree/d3js/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/tree/d3js/collapsibleTree.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/tree/d3js/d3.tree.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/tree/d3js/d3.v4.min.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/tree/d3js/tree.css +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/tree/d3js/tree.html.j2 +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/treemap/Treemap.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/treemap/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/treemap/d3js/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/treemap/d3js/d3.v4.min.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/treemap/d3js/treemap.css +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/treemap/d3js/treemap.html.j2 +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/utils.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/violin/Violin.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/violin/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/violin/d3js/__init__.py +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/violin/d3js/d3-scale-chromatic.v1.min.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/violin/d3js/d3.v4.min.js +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/violin/d3js/violin.css +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks/violin/d3js/violin.html.j2 +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks.egg-info/dependency_links.txt +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks.egg-info/requires.txt +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/d3blocks.egg-info/top_level.txt +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/pyproject.toml +0 -0
- {d3blocks-2.3.0 → d3blocks-2.3.1}/setup.cfg +0 -0
|
@@ -1386,7 +1386,7 @@ class D3Blocks():
|
|
|
1386
1386
|
opacity: float = 0.6,
|
|
1387
1387
|
stroke: str = '#000000',
|
|
1388
1388
|
cmap: str = 'Set1',
|
|
1389
|
-
color_method: str = '
|
|
1389
|
+
color_method: str = 'auto',
|
|
1390
1390
|
dt_format: str = '%d-%m-%Y %H:%M:%S',
|
|
1391
1391
|
damper: float = 1,
|
|
1392
1392
|
fontsize: int = 14,
|
|
@@ -1440,12 +1440,14 @@ class D3Blocks():
|
|
|
1440
1440
|
* [4, 10, 5, ...]: array-like of the same length as ``df``, aligned positionally with its rows (same convention as ``d3.scatter``'s ``size``).
|
|
1441
1441
|
* {'0': 4, '1': 10, '2': 5, ..}: dict keyed by sample_id value.
|
|
1442
1442
|
|
|
1443
|
-
color :
|
|
1443
|
+
color : str, list/array/Series, or dictionary. (default: None)
|
|
1444
1444
|
Color the nodes.
|
|
1445
1445
|
* '#000FFF': set all nodes to this color.
|
|
1446
|
-
* ['#808080', '#FFF000', ...]: array-like of the same length as ``df``, aligned positionally with its rows.
|
|
1447
|
-
*
|
|
1448
|
-
*
|
|
1446
|
+
* ['#808080', '#FFF000', ...]: array-like hex colors of the same length as ``df``, aligned positionally with its rows.
|
|
1447
|
+
* ['Netherlands', 'Germany', ...]: array-like category labels of the same length as ``df``; labels are mapped to colors using ``cmap``.
|
|
1448
|
+
* {'0': '#808080', '1': '#FFF000', '3': '#000000', ..}: dict keyed by sample_id value with hex colors.
|
|
1449
|
+
* {'0': 'NL', '1': 'DE', ..}: dict keyed by sample_id value with category labels; labels are mapped to colors using ``cmap``.
|
|
1450
|
+
* None: Colors are based on sample_id using ``cmap``.
|
|
1449
1451
|
opacity : float, list/array/Series, or dictionary, (default: 0.6)
|
|
1450
1452
|
Opacity of the bubbles [0-1].
|
|
1451
1453
|
* 0.6: set all nodes to this opacity.
|
|
@@ -1458,9 +1460,10 @@ class D3Blocks():
|
|
|
1458
1460
|
* {'0': '#000000', '1': '#ffffff', ..}: dict keyed by sample_id value.
|
|
1459
1461
|
|
|
1460
1462
|
color_method : str
|
|
1461
|
-
Coloring of the nodes.
|
|
1462
|
-
* '
|
|
1463
|
-
* '
|
|
1463
|
+
Coloring mode of the nodes.
|
|
1464
|
+
* 'auto' (default): if ``color`` is provided, use node colors ('node'); otherwise use state colors ('state').
|
|
1465
|
+
* 'state': Use the colors defined per state (``d3.node_properties``), so bubble colors change with activity/state.
|
|
1466
|
+
* 'node': Use the colors defined on nodes (from ``color`` / ``d3.edge_properties``), so each sample keeps its assigned color.
|
|
1464
1467
|
dt_format : str
|
|
1465
1468
|
Date time format.
|
|
1466
1469
|
* '%d-%m-%Y %H:%M:%S'.
|
|
@@ -1634,7 +1637,7 @@ class D3Blocks():
|
|
|
1634
1637
|
# Store chart
|
|
1635
1638
|
self.chart = set_chart_func('Movingbubbles', logger)
|
|
1636
1639
|
# Store properties
|
|
1637
|
-
self.config = self.chart.set_config(config=self.config, filepath=filepath, title=title, showfig=showfig, overwrite=overwrite, figsize=figsize, timedelta=timedelta, speed=speed, damper=damper, note=note, time_notes=time_notes, fontsize=fontsize, standardize=standardize, center=center, datetime=datetime, sample_id=sample_id, state=state, reset_properties=reset_properties, cmap=cmap, dt_format=dt_format, notebook=notebook, color_method=color_method, save_button=save_button, show_side_panel=show_side_panel, show_top_panel=show_top_panel, dark_mode=dark_mode, color_background=color_background, node_text_inside=node_text_inside, opacity=opacity, stroke=stroke, logger=logger)
|
|
1640
|
+
self.config = self.chart.set_config(config=self.config, filepath=filepath, title=title, showfig=showfig, overwrite=overwrite, figsize=figsize, timedelta=timedelta, speed=speed, damper=damper, note=note, time_notes=time_notes, fontsize=fontsize, standardize=standardize, center=center, datetime=datetime, sample_id=sample_id, state=state, reset_properties=reset_properties, cmap=cmap, dt_format=dt_format, notebook=notebook, color_method=color_method, color=color, save_button=save_button, show_side_panel=show_side_panel, show_top_panel=show_top_panel, dark_mode=dark_mode, color_background=color_background, node_text_inside=node_text_inside, opacity=opacity, stroke=stroke, logger=logger)
|
|
1638
1641
|
# Set node properties
|
|
1639
1642
|
if self.config['reset_properties'] or (not hasattr(self, 'node_properties')):
|
|
1640
1643
|
self.set_node_properties(df[self.config['state']].values, center=self.config['center'], cmap=self.config['cmap'], logger=logger)
|
|
@@ -24,6 +24,25 @@ except:
|
|
|
24
24
|
from utils import convert_dataframe_dict, set_path, pre_processing, update_config, write_html_file, include_save_to_svg_script, set_logo, resolve_color_background
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
def _resolve_color_method(color_method, color_provided=False, logger=None):
|
|
28
|
+
"""Resolve color_method to one of {'STATE','NODE'}.
|
|
29
|
+
|
|
30
|
+
AUTO resolves to NODE when user-supplied colors are provided, otherwise
|
|
31
|
+
to STATE so state/activity colors remain the default behavior.
|
|
32
|
+
"""
|
|
33
|
+
cm = str('AUTO' if color_method is None else color_method).upper()
|
|
34
|
+
if cm == 'AUTO':
|
|
35
|
+
resolved = 'NODE' if color_provided else 'STATE'
|
|
36
|
+
if logger is not None:
|
|
37
|
+
logger.info('[Movingbubbles] color_method="auto" resolved to [%s].' % (resolved.lower()))
|
|
38
|
+
return resolved
|
|
39
|
+
if cm not in {'STATE', 'NODE'}:
|
|
40
|
+
if logger is not None:
|
|
41
|
+
logger.warning('[Movingbubbles] Unknown color_method [%s]. Falling back to [state].' % (cm))
|
|
42
|
+
return 'STATE'
|
|
43
|
+
return cm
|
|
44
|
+
|
|
45
|
+
|
|
27
46
|
# %% Set configuration properties
|
|
28
47
|
def set_config(config={}, **kwargs):
|
|
29
48
|
"""Set the default configuration settings."""
|
|
@@ -50,7 +69,12 @@ def set_config(config={}, **kwargs):
|
|
|
50
69
|
config['dt_format'] = kwargs.get('dt_format', '%d-%m-%Y %H:%M:%S')
|
|
51
70
|
config['columns'] = kwargs.get('columns', {'datetime': config['datetime'], 'sample_id': config['sample_id'], 'state': config['state']})
|
|
52
71
|
config['notebook'] = kwargs.get('notebook', False)
|
|
53
|
-
config['
|
|
72
|
+
config['color_provided'] = kwargs.get('color_provided', kwargs.get('color', None) is not None)
|
|
73
|
+
config['color_method'] = _resolve_color_method(
|
|
74
|
+
kwargs.get('color_method', 'AUTO'),
|
|
75
|
+
color_provided=config['color_provided'],
|
|
76
|
+
logger=logger,
|
|
77
|
+
)
|
|
54
78
|
config['save_button'] = kwargs.get('save_button', True)
|
|
55
79
|
config['show_side_panel'] = kwargs.get('show_side_panel', True)
|
|
56
80
|
config['show_top_panel'] = kwargs.get('show_top_panel', True)
|
|
@@ -173,7 +197,8 @@ def set_edge_properties(df, **kwargs):
|
|
|
173
197
|
color = kwargs.get('color', None)
|
|
174
198
|
opacity = kwargs.get('opacity', 0.6)
|
|
175
199
|
stroke = kwargs.get('stroke', '#000000')
|
|
176
|
-
|
|
200
|
+
config = kwargs.get('config', {})
|
|
201
|
+
cmap = kwargs.get('cmap', config.get('cmap', 'Set1'))
|
|
177
202
|
dt_format = kwargs.get('dt_format', '%d-%m-%Y %H:%M:%S')
|
|
178
203
|
logger = kwargs.get('logger', None)
|
|
179
204
|
df = df.copy()
|
|
@@ -205,28 +230,46 @@ def _array_like_len_check(name, values, n):
|
|
|
205
230
|
raise Exception('[Movingbubbles] Input parameter [%s] as an array/list/Series must have the same length as the input dataframe: expected %d, got %d.' % (name, n, len(values)))
|
|
206
231
|
|
|
207
232
|
|
|
233
|
+
def _is_hex_color(value):
|
|
234
|
+
return isinstance(value, str) and bool(re.fullmatch(r'#[0-9a-fA-F]{6}', value))
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _labels_to_hex(labels, cmap):
|
|
238
|
+
labels = pd.Series(labels, copy=False).astype(str).values
|
|
239
|
+
colors = colourmap.fromlist(labels, cmap=cmap, scheme='hex', method='matplotlib', verbose=0)[0]
|
|
240
|
+
return [str(c) for c in colors]
|
|
241
|
+
|
|
242
|
+
|
|
208
243
|
def _set_nodecolor(df, sample_id, color, cmap, logger):
|
|
209
244
|
if isinstance(color, dict):
|
|
210
|
-
# Per-node colors supplied as {sample_id: hex
|
|
245
|
+
# Per-node colors supplied as {sample_id: color}. Values can be hex
|
|
246
|
+
# colors or categorical labels that are mapped via cmap.
|
|
211
247
|
if logger is not None: logger.info('Processing the specified node colors as a dictionary..')
|
|
212
248
|
df['color'] = '#808080'
|
|
213
|
-
|
|
214
|
-
|
|
249
|
+
keys = list(color.keys())
|
|
250
|
+
vals = list(color.values())
|
|
251
|
+
if len(vals) > 0 and not np.all([_is_hex_color(v) for v in vals]):
|
|
252
|
+
vals = _labels_to_hex(vals, cmap)
|
|
253
|
+
for key, val in zip(keys, vals):
|
|
254
|
+
df.loc[df[sample_id] == key, 'color'] = str(val)
|
|
215
255
|
elif isinstance(color, (list, tuple, np.ndarray, pd.Series)):
|
|
216
256
|
# Array-like, aligned positionally with the dataframe rows.
|
|
217
257
|
color_arr = np.asarray(color)
|
|
218
258
|
_array_like_len_check('color', color_arr, len(df))
|
|
219
|
-
if
|
|
220
|
-
|
|
259
|
+
if np.all([_is_hex_color(v) for v in color_arr]):
|
|
260
|
+
if logger is not None: logger.info('Processing the specified node colors as hex colors aligned with the dataframe..')
|
|
261
|
+
df['color'] = [str(c) for c in color_arr]
|
|
262
|
+
else:
|
|
263
|
+
if logger is not None: logger.info('Processing the specified node colors as labels aligned with the dataframe and mapping via cmap..')
|
|
264
|
+
df['color'] = _labels_to_hex(color_arr, cmap)
|
|
221
265
|
elif color is None:
|
|
222
266
|
# Derive colors from cmap based on sample_id
|
|
223
|
-
|
|
224
|
-
df['color'] = [str(c) for c in colors]
|
|
267
|
+
df['color'] = _labels_to_hex(df[sample_id], cmap)
|
|
225
268
|
else:
|
|
226
269
|
# Single hex color string applied to all nodes
|
|
227
270
|
df['color'] = str(color)
|
|
228
271
|
if logger is not None: logger.info('Set all nodes to color: %s' %(color))
|
|
229
|
-
|
|
272
|
+
|
|
230
273
|
return df
|
|
231
274
|
|
|
232
275
|
|
|
@@ -331,6 +374,10 @@ def show(df, **kwargs):
|
|
|
331
374
|
sid_col = config['columns']['sample_id']
|
|
332
375
|
sid = np.array(list(map(lambda x: labels.get(x)['id'], df[config['columns']['state']].values)))
|
|
333
376
|
uiid = np.unique(df[sid_col])
|
|
377
|
+
# size/opacity schedules: one list per sample, aligned with that sample's
|
|
378
|
+
# activity steps so the frontend can update radius/opacity on state change.
|
|
379
|
+
node_size_sched = []
|
|
380
|
+
node_opacity_sched = []
|
|
334
381
|
for i in uiid:
|
|
335
382
|
# Combine the sample_id with its time in state
|
|
336
383
|
Iloc=df[sid_col]==i
|
|
@@ -346,19 +393,25 @@ def show(df, **kwargs):
|
|
|
346
393
|
# Make one big happy list
|
|
347
394
|
X.append(tmplist)
|
|
348
395
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
396
|
+
size_vals = df['size'].loc[Iloc].values
|
|
397
|
+
node_size_sched.append([
|
|
398
|
+
int(v) if isinstance(v, (np.integer, int)) else float(v)
|
|
399
|
+
for v in size_vals
|
|
400
|
+
])
|
|
401
|
+
opacity_vals = df['opacity'].loc[Iloc].values
|
|
402
|
+
node_opacity_sched.append([float(v) for v in opacity_vals])
|
|
403
|
+
|
|
404
|
+
# Per-sample size/opacity schedules (list of lists, same order as uiid /
|
|
405
|
+
# schedule steps). Frontend uses index 0 at start and updates on each
|
|
406
|
+
# activity transition so size/opacity track the current state.
|
|
407
|
+
config['node_size'] = node_size_sched
|
|
408
|
+
config['node_opacity'] = node_opacity_sched
|
|
352
409
|
|
|
353
410
|
# Node color in the same order as the uiid
|
|
354
411
|
nodedict = dict(zip(df[sid_col], df['color']))
|
|
355
412
|
# Convert NumPy strings to regular Python strings for proper JSON serialization
|
|
356
413
|
config['node_color'] = [str(nodedict.get(x)) if nodedict.get(x) is not None else nodedict.get(x) for x in uiid]
|
|
357
414
|
|
|
358
|
-
# Node opacity in the same order as the uiid
|
|
359
|
-
nodedict = dict(zip(df[sid_col], df['opacity']))
|
|
360
|
-
config['node_opacity'] = [float(nodedict.get(x)) for x in uiid]
|
|
361
|
-
|
|
362
415
|
# Node stroke color in the same order as the uiid
|
|
363
416
|
nodedict = dict(zip(df[sid_col], df['stroke']))
|
|
364
417
|
config['node_stroke'] = [str(nodedict.get(x)) if nodedict.get(x) is not None else nodedict.get(x) for x in uiid]
|
|
@@ -523,7 +576,11 @@ def write_html(X, config, logger=None):
|
|
|
523
576
|
zero_to_min = "0" if config['start_minute']<10 else ""
|
|
524
577
|
|
|
525
578
|
# Set the selectionbox correctly on the form
|
|
526
|
-
config['color_method'] =
|
|
579
|
+
config['color_method'] = _resolve_color_method(
|
|
580
|
+
config.get('color_method', 'AUTO'),
|
|
581
|
+
color_provided=config.get('color_provided', False),
|
|
582
|
+
logger=logger,
|
|
583
|
+
)
|
|
527
584
|
SELECTED_STATE = {'STATE': '', 'NODE': ''}
|
|
528
585
|
SELECTED_STATE[config['color_method']] = 'selected="selected"'
|
|
529
586
|
bg_dark, bg_light = resolve_color_background(config.get('color_background'))
|
|
@@ -553,6 +610,7 @@ def write_html(X, config, logger=None):
|
|
|
553
610
|
'NOTE': config['note'],
|
|
554
611
|
'TIME_NOTES': config['time_notes'],
|
|
555
612
|
'COLOR_METHOD': config['color_method'],
|
|
613
|
+
'COLOR_PROVIDED': 'true' if config.get('color_provided', False) else 'false',
|
|
556
614
|
'START_HOUR_MIN': config['start_hour'] + (config['start_minute'] / 60),
|
|
557
615
|
'START_TIME': zero_to_hour + str(config['start_hour']) + ":" + zero_to_min + str(config['start_minute']),
|
|
558
616
|
'TIMEDELTA': config['timedelta'],
|
|
@@ -260,9 +260,23 @@ var notes_index = 0;
|
|
|
260
260
|
var colorByActivity = {{ COLORBYACTIVITY }} // array of the colors per state
|
|
261
261
|
|
|
262
262
|
// NODE PROPERTIES
|
|
263
|
-
var node_size = {{ NODE_SIZE }}; //
|
|
263
|
+
var node_size = {{ NODE_SIZE }}; // per-node size schedule: list of lists (one value per activity step), or legacy flat array
|
|
264
264
|
var node_color = {{ NODE_COLOR }}; //array of color values, one for each node
|
|
265
|
-
var node_opacity = {{ NODE_OPACITY }}; //
|
|
265
|
+
var node_opacity = {{ NODE_OPACITY }}; // per-node opacity schedule: list of lists (one value per activity step), or legacy flat array
|
|
266
|
+
|
|
267
|
+
// Resolve size/opacity for node i at schedule step idx.
|
|
268
|
+
// Supports both schedule arrays (list-of-lists from Python) and legacy
|
|
269
|
+
// flat arrays / scalars (one value per node).
|
|
270
|
+
function sizeAt(i, idx) {
|
|
271
|
+
var s = node_size[i];
|
|
272
|
+
if (Array.isArray(s)) return +s[Math.min(idx, s.length - 1)];
|
|
273
|
+
return +s;
|
|
274
|
+
}
|
|
275
|
+
function opacityAt(i, idx) {
|
|
276
|
+
var o = node_opacity[i];
|
|
277
|
+
if (Array.isArray(o)) return +o[Math.min(idx, o.length - 1)];
|
|
278
|
+
return +o;
|
|
279
|
+
}
|
|
266
280
|
var node_stroke = {{ NODE_STROKE }}; //array of stroke colors, one for each node
|
|
267
281
|
var nodeTextInside = {{ nodeTextInside }}; //true: labels centered inside the node; false: labels above it
|
|
268
282
|
|
|
@@ -283,10 +297,15 @@ var PROP_KEYS = {{ PROP_KEYS }}; // ordered list of all selectable variables
|
|
|
283
297
|
var STATE_COL = {{ STATE_COL }}; // name of the state column (live-updated)
|
|
284
298
|
var SAMPLE_ID_COL = {{ SAMPLE_ID_COL }};
|
|
285
299
|
var color_method = "{{ COLOR_METHOD }}"; //Nodes can be colored based on "STATE" or "NODE"
|
|
300
|
+
var color_provided = {{ COLOR_PROVIDED }};
|
|
301
|
+
var COLOR_MODE_STATE = "__mode_state__";
|
|
302
|
+
var COLOR_MODE_CUSTOM = "__mode_custom__";
|
|
303
|
+
var customColorLabel = color_provided ? "custom" : "node";
|
|
286
304
|
// Layout-panel overrides (null = use defaults / color_method)
|
|
287
305
|
var layoutSizeKey = null;
|
|
288
306
|
var layoutColorKey = null;
|
|
289
307
|
var layoutColorScale = null; // function(value) -> color, when layoutColorKey is set
|
|
308
|
+
var colorModeInitialized = false;
|
|
290
309
|
var currentShapeMode = "circle"; // circle|square|triangle|diamond
|
|
291
310
|
var categoricalShapeMap = null;
|
|
292
311
|
var categoricalShapeKey = null;
|
|
@@ -420,13 +439,14 @@ var nodes = sched_objs.map(function(o, i) {
|
|
|
420
439
|
act: act,
|
|
421
440
|
x: init_x,
|
|
422
441
|
y: init_y,
|
|
423
|
-
radius:
|
|
442
|
+
radius: sizeAt(i, 0),
|
|
424
443
|
color: (color_method === "STATE" ? color(act) : node_color[i]),
|
|
425
|
-
opacity:
|
|
444
|
+
opacity: opacityAt(i, 0),
|
|
426
445
|
stroke: node_stroke[i],
|
|
427
446
|
moves: 0,
|
|
428
447
|
next_move_time: o[0].duration,
|
|
429
448
|
sched: o,
|
|
449
|
+
nodeIndex: i,
|
|
430
450
|
props: (node_props && node_props[i]) ? node_props[i] : {},
|
|
431
451
|
shape: "circle",
|
|
432
452
|
}
|
|
@@ -730,11 +750,18 @@ function timer() {
|
|
|
730
750
|
curr_node.cx = foci[curr_node.act].x;
|
|
731
751
|
curr_node.cy = foci[curr_node.act].y;
|
|
732
752
|
|
|
753
|
+
// Update size/opacity for the new state (schedule-aligned).
|
|
754
|
+
// baseNodeRadii drives recomputeRadii() so size multiplier /
|
|
755
|
+
// layout size-by-variable still apply on top.
|
|
756
|
+
baseNodeRadii[i] = sizeAt(i, curr_moves);
|
|
757
|
+
curr_node.opacity = opacityAt(i, curr_moves);
|
|
758
|
+
|
|
733
759
|
nodes[i].next_move_time += nodes[i].sched[ curr_node.moves ].duration;
|
|
734
760
|
}
|
|
735
761
|
|
|
736
762
|
});
|
|
737
763
|
|
|
764
|
+
if (typeof recomputeRadii === 'function') recomputeRadii();
|
|
738
765
|
force.resume();
|
|
739
766
|
curr_minute += 1;
|
|
740
767
|
|
|
@@ -817,9 +844,13 @@ function tick(e) {
|
|
|
817
844
|
//}
|
|
818
845
|
|
|
819
846
|
// Base color from state/node, unless Layout→Color overrides it.
|
|
820
|
-
// Special
|
|
821
|
-
|
|
847
|
+
// Special cases:
|
|
848
|
+
// - COLOR_MODE_STATE: live activity colors
|
|
849
|
+
// - COLOR_MODE_CUSTOM: original node colors (custom/node)
|
|
850
|
+
if (layoutColorKey === COLOR_MODE_STATE || layoutColorKey === STATE_COL) {
|
|
822
851
|
o.color = color(curr_act);
|
|
852
|
+
} else if (layoutColorKey === COLOR_MODE_CUSTOM) {
|
|
853
|
+
o.color = node_color[i];
|
|
823
854
|
} else if (layoutColorKey && layoutColorScale && o.props) {
|
|
824
855
|
var pv = o.props[layoutColorKey];
|
|
825
856
|
o.color = (pv != null && pv !== '') ? layoutColorScale(pv) : (color_method === "STATE" ? color(curr_act) : node_color[i]);
|
|
@@ -836,6 +867,7 @@ function tick(e) {
|
|
|
836
867
|
circle
|
|
837
868
|
.each(collide(.5))
|
|
838
869
|
.style("fill", function(d) { return d.color; })
|
|
870
|
+
.style("opacity", function(d) { return d.opacity; })
|
|
839
871
|
.attr("d", function(d) { return shapePath(shapeForNode(d), d.radius); })
|
|
840
872
|
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
|
|
841
873
|
|
|
@@ -945,15 +977,18 @@ function resetSimulation(keepSpeed) {
|
|
|
945
977
|
for (k in act_counts) {
|
|
946
978
|
if (Object.prototype.hasOwnProperty.call(act_counts, k)) act_counts[k] = 0;
|
|
947
979
|
}
|
|
948
|
-
nodes.forEach(function (n) {
|
|
980
|
+
nodes.forEach(function (n, i) {
|
|
949
981
|
var act = n.sched[0].act;
|
|
950
982
|
n.act = act;
|
|
951
983
|
n.moves = 0;
|
|
952
984
|
n.next_move_time = n.sched[0].duration;
|
|
953
985
|
n.x = foci[act].x + Math.random();
|
|
954
986
|
n.y = foci[act].y + Math.random();
|
|
987
|
+
baseNodeRadii[i] = sizeAt(i, 0);
|
|
988
|
+
n.opacity = opacityAt(i, 0);
|
|
955
989
|
act_counts[act] += 1;
|
|
956
990
|
});
|
|
991
|
+
if (typeof recomputeRadii === 'function') recomputeRadii();
|
|
957
992
|
|
|
958
993
|
// Clear selection (trails are intentionally left alone -- only the
|
|
959
994
|
// "Clear trails" button should remove them; Reset restarts positions
|
|
@@ -1289,8 +1324,14 @@ function applyColorMode(key) {
|
|
|
1289
1324
|
layoutColorKey = key || null;
|
|
1290
1325
|
layoutColorScale = null;
|
|
1291
1326
|
if (!key) return;
|
|
1292
|
-
//
|
|
1293
|
-
if (key ===
|
|
1327
|
+
// Built-in base color modes.
|
|
1328
|
+
if (key === COLOR_MODE_STATE || key === COLOR_MODE_CUSTOM) return;
|
|
1329
|
+
// Backward-compatibility: if state column value is selected, treat it as
|
|
1330
|
+
// live state coloring mode.
|
|
1331
|
+
if (key === STATE_COL) {
|
|
1332
|
+
layoutColorKey = COLOR_MODE_STATE;
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1294
1335
|
if (isNumericProp(key)) {
|
|
1295
1336
|
var vals = numericValues(key);
|
|
1296
1337
|
var min = d3.min(vals), max = d3.max(vals);
|
|
@@ -1368,6 +1409,10 @@ function initLayoutPropertyControls() {
|
|
|
1368
1409
|
|
|
1369
1410
|
var prevSize = sizeSelect ? sizeSelect.value : '';
|
|
1370
1411
|
var prevColor = colorSelect ? colorSelect.value : '';
|
|
1412
|
+
if (!colorModeInitialized) {
|
|
1413
|
+
prevColor = (color_method === "STATE") ? COLOR_MODE_STATE : COLOR_MODE_CUSTOM;
|
|
1414
|
+
colorModeInitialized = true;
|
|
1415
|
+
}
|
|
1371
1416
|
var prevShape = shapeSelect ? shapeSelect.value : 'circle';
|
|
1372
1417
|
var prevLabel = labelSelect ? labelSelect.value : '';
|
|
1373
1418
|
|
|
@@ -1387,12 +1432,17 @@ function initLayoutPropertyControls() {
|
|
|
1387
1432
|
o.textContent = label;
|
|
1388
1433
|
select.appendChild(o);
|
|
1389
1434
|
}
|
|
1435
|
+
// Built-in base color modes are always available in the Color dropdown.
|
|
1436
|
+
addOpt(colorSelect, COLOR_MODE_STATE, "state");
|
|
1437
|
+
addOpt(colorSelect, COLOR_MODE_CUSTOM, customColorLabel);
|
|
1438
|
+
|
|
1390
1439
|
keys.forEach(function (k) {
|
|
1391
1440
|
if (!isVarEnabled(k)) return;
|
|
1392
1441
|
// State is always categorical for layout purposes.
|
|
1393
1442
|
var numeric = (k !== STATE_COL) && isNumericProp(k);
|
|
1394
1443
|
if (numeric) addOpt(sizeSelect, k, k);
|
|
1395
|
-
|
|
1444
|
+
// STATE_COL has a dedicated built-in mode label "state".
|
|
1445
|
+
if (k !== STATE_COL) addOpt(colorSelect, k, k);
|
|
1396
1446
|
if (!numeric) addOpt(shapeSelect, "prop:" + k, k);
|
|
1397
1447
|
addOpt(labelSelect, k, k);
|
|
1398
1448
|
});
|
|
@@ -1409,7 +1459,8 @@ function initLayoutPropertyControls() {
|
|
|
1409
1459
|
applySizeMode(sizeSelect.value);
|
|
1410
1460
|
}
|
|
1411
1461
|
if (colorSelect) {
|
|
1412
|
-
|
|
1462
|
+
var colorFallback = (color_method === "STATE") ? COLOR_MODE_STATE : COLOR_MODE_CUSTOM;
|
|
1463
|
+
colorSelect.value = hasOption(colorSelect, prevColor) ? prevColor : colorFallback;
|
|
1413
1464
|
applyColorMode(colorSelect.value);
|
|
1414
1465
|
}
|
|
1415
1466
|
if (shapeSelect) {
|
|
@@ -1523,11 +1574,11 @@ if (clearTrailsBtn) clearTrailsBtn.addEventListener("click", function () {
|
|
|
1523
1574
|
var layoutResetBtn = document.getElementById("layoutResetBtn");
|
|
1524
1575
|
if (layoutResetBtn) layoutResetBtn.addEventListener("click", function () {
|
|
1525
1576
|
if (layoutSizeSelect) layoutSizeSelect.value = "";
|
|
1526
|
-
if (layoutColorSelect) layoutColorSelect.value = "";
|
|
1577
|
+
if (layoutColorSelect) layoutColorSelect.value = (color_method === "STATE") ? COLOR_MODE_STATE : COLOR_MODE_CUSTOM;
|
|
1527
1578
|
if (layoutShapeSelect) layoutShapeSelect.value = "circle";
|
|
1528
1579
|
if (layoutLabelSelect) layoutLabelSelect.value = "";
|
|
1529
1580
|
applySizeMode("");
|
|
1530
|
-
applyColorMode("");
|
|
1581
|
+
applyColorMode(layoutColorSelect ? layoutColorSelect.value : "");
|
|
1531
1582
|
applyShapeMode("circle");
|
|
1532
1583
|
applyLabelMode("");
|
|
1533
1584
|
});
|
|
@@ -1595,15 +1646,18 @@ function scrubToMinute(targetMinute) {
|
|
|
1595
1646
|
if (Object.prototype.hasOwnProperty.call(act_counts, k)) act_counts[k] = 0;
|
|
1596
1647
|
}
|
|
1597
1648
|
|
|
1598
|
-
nodes.forEach(function (n) {
|
|
1649
|
+
nodes.forEach(function (n, i) {
|
|
1599
1650
|
var s = nodeStateAtMinute(n, targetMinute);
|
|
1600
1651
|
n.act = s.act;
|
|
1601
1652
|
n.moves = s.moves;
|
|
1602
1653
|
n.next_move_time = s.next_move_time;
|
|
1654
|
+
baseNodeRadii[i] = sizeAt(i, s.moves);
|
|
1655
|
+
n.opacity = opacityAt(i, s.moves);
|
|
1603
1656
|
// Do NOT snap x/y — let the force layout pull each bubble
|
|
1604
1657
|
// smoothly toward its new focus so nothing teleports or explodes.
|
|
1605
1658
|
act_counts[n.act] += 1;
|
|
1606
1659
|
});
|
|
1660
|
+
if (typeof recomputeRadii === 'function') recomputeRadii();
|
|
1607
1661
|
|
|
1608
1662
|
curr_minute = targetMinute;
|
|
1609
1663
|
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import colourmap
|
|
2
|
+
import pandas as pd
|
|
3
|
+
|
|
4
|
+
from d3blocks.movingbubbles.Movingbubbles import set_config, set_edge_properties
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _make_df():
|
|
8
|
+
return pd.DataFrame(
|
|
9
|
+
{
|
|
10
|
+
"row_id": [0, 1, 2, 3],
|
|
11
|
+
"datetime": pd.to_datetime(
|
|
12
|
+
[
|
|
13
|
+
"2023-01-01 00:00:00",
|
|
14
|
+
"2023-01-01 00:01:00",
|
|
15
|
+
"2023-01-01 00:02:00",
|
|
16
|
+
"2023-01-01 00:03:00",
|
|
17
|
+
]
|
|
18
|
+
),
|
|
19
|
+
"sample_id": [1, 2, 1, 2],
|
|
20
|
+
"state": ["A", "B", "A", "B"],
|
|
21
|
+
}
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_movingbubbles_color_series_categories_uses_cmap():
|
|
26
|
+
df = _make_df()
|
|
27
|
+
categories = pd.Series(["Netherlands", "Germany", "Netherlands", "Norway"])
|
|
28
|
+
|
|
29
|
+
out = set_edge_properties(
|
|
30
|
+
df,
|
|
31
|
+
datetime="datetime",
|
|
32
|
+
sample_id="sample_id",
|
|
33
|
+
state="state",
|
|
34
|
+
color=categories,
|
|
35
|
+
standardize="samplewise",
|
|
36
|
+
timedelta="minutes",
|
|
37
|
+
config={"cmap": "Dark2"},
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
expected = [
|
|
41
|
+
str(c)
|
|
42
|
+
for c in colourmap.fromlist(
|
|
43
|
+
categories.astype(str).values,
|
|
44
|
+
cmap="Dark2",
|
|
45
|
+
scheme="hex",
|
|
46
|
+
method="matplotlib",
|
|
47
|
+
verbose=0,
|
|
48
|
+
)[0]
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
out_by_input_order = out.sort_values("row_id").reset_index(drop=True)
|
|
52
|
+
assert out_by_input_order["color"].tolist() == expected
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_movingbubbles_color_method_auto_resolves_to_node_when_color_given():
|
|
56
|
+
cfg = set_config(config={}, color_method="auto", color=["A", "B"])
|
|
57
|
+
assert cfg["color_method"] == "NODE"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_movingbubbles_color_method_auto_resolves_to_state_without_color():
|
|
61
|
+
cfg = set_config(config={}, color_method="auto", color=None)
|
|
62
|
+
assert cfg["color_method"] == "STATE"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_movingbubbles_color_dict_categories_uses_cmap():
|
|
66
|
+
df = _make_df()
|
|
67
|
+
category_by_sample = {1: "group_a", 2: "group_b"}
|
|
68
|
+
|
|
69
|
+
out = set_edge_properties(
|
|
70
|
+
df,
|
|
71
|
+
datetime="datetime",
|
|
72
|
+
sample_id="sample_id",
|
|
73
|
+
state="state",
|
|
74
|
+
color=category_by_sample,
|
|
75
|
+
standardize="samplewise",
|
|
76
|
+
timedelta="minutes",
|
|
77
|
+
cmap="Set2",
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
mapped = [
|
|
81
|
+
str(c)
|
|
82
|
+
for c in colourmap.fromlist(
|
|
83
|
+
list(category_by_sample.values()),
|
|
84
|
+
cmap="Set2",
|
|
85
|
+
scheme="hex",
|
|
86
|
+
method="matplotlib",
|
|
87
|
+
verbose=0,
|
|
88
|
+
)[0]
|
|
89
|
+
]
|
|
90
|
+
expected_by_row = [mapped[0], mapped[1], mapped[0], mapped[1]]
|
|
91
|
+
|
|
92
|
+
out_by_input_order = out.sort_values("row_id").reset_index(drop=True)
|
|
93
|
+
assert out_by_input_order["color"].tolist() == expected_by_row
|
|
@@ -136,6 +136,7 @@ d3blocks/tests/test_d3blocks.py
|
|
|
136
136
|
d3blocks/tests/test_erd.py
|
|
137
137
|
d3blocks/tests/test_flowgraph.py
|
|
138
138
|
d3blocks/tests/test_medium_charts.py
|
|
139
|
+
d3blocks/tests/test_movingbubbles_color.py
|
|
139
140
|
d3blocks/tests/test_movingbubbles_timedelta.py
|
|
140
141
|
d3blocks/tests/test_radialgraph.py
|
|
141
142
|
d3blocks/tests/test_sequence.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|