plot3 0.6.3__tar.gz → 0.6.5__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.
- {plot3-0.6.3 → plot3-0.6.5}/PKG-INFO +5 -1
- {plot3-0.6.3 → plot3-0.6.5}/README.md +4 -0
- plot3-0.6.5/plot3/__version__.py +1 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3/viewer.py +122 -15
- {plot3-0.6.3 → plot3-0.6.5}/plot3.egg-info/PKG-INFO +5 -1
- plot3-0.6.3/plot3/__version__.py +0 -1
- {plot3-0.6.3 → plot3-0.6.5}/LICENSE +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3/__init__.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3/aesexpr.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3/build.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3/calculus.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3/compose.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3/contour.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3/craft.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3/encode.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3/expr.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3/flip.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3/function.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3/geoms.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3/ggplot.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3/io.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3/jupyter.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3/latexin.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3/masking.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3/mathtext.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3/payload.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3/remote.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3/scales.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3/scaling.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3/special.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3/stat2d.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3/static.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3/stats3d.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3/table.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3/themes.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3.egg-info/SOURCES.txt +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3.egg-info/dependency_links.txt +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3.egg-info/requires.txt +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/plot3.egg-info/top_level.txt +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/pyproject.toml +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/setup.cfg +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_2d_density.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_3d.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_animation.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_annotate.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_api_public.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_array_backend.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_arrow.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_build_contract.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_contour.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_everyday_ggplot2.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_expr.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_facets_compose.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_geom_function.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_geoms_ds.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_ggplot2_extras2.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_ggplot2_parity.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_ggplot_extras.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_grammar.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_latex_input.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_lidar_scene.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_masking.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_math_layers.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_mathtext.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_payload.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_payload_display.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_point_cloud.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_r_oracle_parity.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_readme.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_remote_bridge.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_review_fixes.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_save_menu.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_scales_api.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_scales_themes.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_special.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_stat2d.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_static_save.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_stats_semantic.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_step3_geoms.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_table_backends.py +0 -0
- {plot3-0.6.3 → plot3-0.6.5}/tests/test_titles_theme.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plot3
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.5
|
|
4
4
|
Summary: ggplot2 grammar of graphics for Python: interactive WebGL figures and journal-ready PNG, SVG, and PDF
|
|
5
5
|
Author: Rigoberto Leyva Salmeron
|
|
6
6
|
License-Expression: MIT
|
|
@@ -130,6 +130,10 @@ gapminder = pd.read_csv("https://raw.githubusercontent.com/kirenz/datasets/maste
|
|
|
130
130
|
`{frame_time}` in the title shows the current year as it plays.
|
|
131
131
|
`transition_states("phase")` steps through categories instead of time.
|
|
132
132
|
|
|
133
|
+
Click a bubble to trace it: its path through every frame appears, with
|
|
134
|
+
the years along it and its name (from `group=`) riding beside it as it
|
|
135
|
+
plays. Click it again, or empty space, to clear it. This works in 3D too.
|
|
136
|
+
|
|
133
137
|
<details>
|
|
134
138
|
<summary>Run this without downloading anything (made-up gapminder-shaped data)</summary>
|
|
135
139
|
|
|
@@ -103,6 +103,10 @@ gapminder = pd.read_csv("https://raw.githubusercontent.com/kirenz/datasets/maste
|
|
|
103
103
|
`{frame_time}` in the title shows the current year as it plays.
|
|
104
104
|
`transition_states("phase")` steps through categories instead of time.
|
|
105
105
|
|
|
106
|
+
Click a bubble to trace it: its path through every frame appears, with
|
|
107
|
+
the years along it and its name (from `group=`) riding beside it as it
|
|
108
|
+
plays. Click it again, or empty space, to clear it. This works in 3D too.
|
|
109
|
+
|
|
106
110
|
<details>
|
|
107
111
|
<summary>Run this without downloading anything (made-up gapminder-shaped data)</summary>
|
|
108
112
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.6.5"
|
|
@@ -55,6 +55,9 @@ body{display:flex;flex-direction:column}
|
|
|
55
55
|
#legend .sz-block{clear:both;padding-top:6px}
|
|
56
56
|
#legend .bub{display:inline-block;border-radius:50%;box-sizing:border-box;flex:none;
|
|
57
57
|
border:1px solid rgba(0,0,0,.35)}
|
|
58
|
+
#legend.docked{display:flex!important;flex-wrap:wrap;align-items:center;column-gap:14px;row-gap:0;line-height:1.5}
|
|
59
|
+
#legend.docked .sz-block{display:flex;flex-wrap:wrap;align-items:center;column-gap:10px;padding-top:0;clear:none}
|
|
60
|
+
#legend.docked .sz{margin:0}
|
|
58
61
|
#player{display:none;flex:none;align-items:center;flex-wrap:wrap;gap:8px;padding:4px 10px 8px;max-width:100%;box-sizing:border-box}
|
|
59
62
|
#player button{font:inherit;padding:3px 10px;border-radius:5px;cursor:pointer}
|
|
60
63
|
#player #play-range{flex:1;min-width:80px}
|
|
@@ -345,19 +348,25 @@ function showLegendBox() {
|
|
|
345
348
|
legEl.style.border = '1px solid ' + T.grid;
|
|
346
349
|
legEl.style.color = T.ink2;
|
|
347
350
|
}
|
|
351
|
+
// Height the docked legend takes at the bottom; the plot shrinks by it.
|
|
352
|
+
let legendReserve = 0;
|
|
348
353
|
function placeLegend() {
|
|
349
|
-
|
|
354
|
+
legendReserve = 0;
|
|
355
|
+
if (!legEl || legEl.style.display === 'none') return;
|
|
350
356
|
const figH = figEl.clientHeight || 0;
|
|
351
357
|
if (figH < 40) return;
|
|
352
|
-
// A key taller than the panel covers the
|
|
353
|
-
|
|
358
|
+
// A key taller than the panel covers the data (a short notebook output).
|
|
359
|
+
// Lay it out in rows under the axes, and keep that room free.
|
|
360
|
+
if (legEl.dataset.docked !== '1' && legEl.offsetHeight > figH * 0.40) {
|
|
354
361
|
legEl.dataset.docked = '1';
|
|
362
|
+
legEl.classList.add('docked');
|
|
355
363
|
legEl.style.top = 'auto';
|
|
356
|
-
legEl.style.bottom = '
|
|
364
|
+
legEl.style.bottom = '6px';
|
|
357
365
|
legEl.style.left = '8px';
|
|
358
366
|
legEl.style.right = '8px';
|
|
359
367
|
legEl.style.maxWidth = 'none';
|
|
360
368
|
}
|
|
369
|
+
if (legEl.dataset.docked === '1') legendReserve = legEl.offsetHeight + 8;
|
|
361
370
|
}
|
|
362
371
|
const LEGEND_GLYPH = { circle: '●', triangle: '▲', square: '■', diamond: '◆', plus: '+', cross: '×' };
|
|
363
372
|
// Legend keys: a square, the point's symbol, or a short (dashed) line.
|
|
@@ -693,6 +702,26 @@ const stepEntries = [];
|
|
|
693
702
|
let hoverHold = false;
|
|
694
703
|
let trail = null;
|
|
695
704
|
let trailId = null;
|
|
705
|
+
// The clicked bubble's path: points with their frame, its name and colour.
|
|
706
|
+
let trailInfo = null;
|
|
707
|
+
// A click toggles a bubble's path only on the bubble itself (a little
|
|
708
|
+
// slack for tiny dots), so a stray click near one draws nothing.
|
|
709
|
+
function clickRadius(L, i, minimum) {
|
|
710
|
+
return L._size ? Math.max(minimum, L._size[i] * 0.55) : minimum;
|
|
711
|
+
}
|
|
712
|
+
function trailKey(L, i) { return String(i) + '|' + (L.ids ? L.ids[i] : ''); }
|
|
713
|
+
function trailHint(L, i) {
|
|
714
|
+
return '<br><span style="opacity:.6;font-size:10px">click to ' +
|
|
715
|
+
(trailId === trailKey(L, i) ? 'hide' : 'show') + ' its path</span>';
|
|
716
|
+
}
|
|
717
|
+
// The frame's value (the year) for a point on the path, or '' if unknown.
|
|
718
|
+
function trailYear(f) {
|
|
719
|
+
const tr = S.transition;
|
|
720
|
+
if (!tr || !tr.times || !trailInfo || tr.times.length !== trailInfo.nF) return '';
|
|
721
|
+
const v = tr.times[f];
|
|
722
|
+
if (tr.integer) return String(Math.round(+v));
|
|
723
|
+
return typeof v === 'number' ? fmt(v) : String(v);
|
|
724
|
+
}
|
|
696
725
|
function framePresent(F, i) {
|
|
697
726
|
if (!F.x || !F.x.maskData || !F.x.maskData[i]) return false;
|
|
698
727
|
if (!F.y || !F.y.maskData || !F.y.maskData[i]) return false;
|
|
@@ -1158,6 +1187,7 @@ function clearTrail() {
|
|
|
1158
1187
|
trail = null;
|
|
1159
1188
|
}
|
|
1160
1189
|
trailId = null;
|
|
1190
|
+
trailInfo = null;
|
|
1161
1191
|
}
|
|
1162
1192
|
function setTrail(L, obj) {
|
|
1163
1193
|
clearTrail();
|
|
@@ -1199,7 +1229,18 @@ function setTrail(L, obj) {
|
|
|
1199
1229
|
}
|
|
1200
1230
|
scene.add(group);
|
|
1201
1231
|
trail = group;
|
|
1202
|
-
trailId =
|
|
1232
|
+
trailId = trailKey(L, obj);
|
|
1233
|
+
const pts = [];
|
|
1234
|
+
for (let f = 0; f < nF; f++) {
|
|
1235
|
+
const i = obj * nF + f;
|
|
1236
|
+
if (!framePresent(F, i)) continue;
|
|
1237
|
+
pts.push({ x: F.x.data[i], y: F.y.data[i], z: F.z ? F.z.data[i] : 0, f: f });
|
|
1238
|
+
}
|
|
1239
|
+
trailInfo = {
|
|
1240
|
+
L: L, obj: obj, nF: nF, ext: ext, pts: pts,
|
|
1241
|
+
name: L.ids && L.ids[obj] != null ? String(L.ids[obj]) : '',
|
|
1242
|
+
color: 'rgb(' + rgb.map(c => Math.round(c * 255)).join(',') + ')',
|
|
1243
|
+
};
|
|
1203
1244
|
}
|
|
1204
1245
|
let frameLabel = '';
|
|
1205
1246
|
const axisTitleSprites = [];
|
|
@@ -2029,14 +2070,52 @@ if (!S.is3d) {
|
|
|
2029
2070
|
const AY = Math.min(Math.max(Y, M.t + 12), M.t + H - 10);
|
|
2030
2071
|
s += '<text x="' + AX + '" y="' + AY + '" fill="' + T.ink + '" text-anchor="middle" dominant-baseline="middle" font-size="12" font-weight="600" paint-order="stroke" stroke="' + T.surface + '" stroke-width="4" stroke-linejoin="round">' + plot3Esc(ann.text) + '</text>';
|
|
2031
2072
|
}
|
|
2073
|
+
if (trailInfo) s += trailLabels2D();
|
|
2032
2074
|
svg.innerHTML = s;
|
|
2033
2075
|
if (gridSvg) gridSvg.innerHTML = grid;
|
|
2034
2076
|
}
|
|
2035
2077
|
|
|
2078
|
+
// The clicked bubble's name rides beside it; years mark its path,
|
|
2079
|
+
// spaced so they do not overlap (the first and last always show).
|
|
2080
|
+
function trailLabels2D() {
|
|
2081
|
+
const t = trailInfo, ext = t.ext;
|
|
2082
|
+
const inside = (X, Y) => X >= M.l && X <= M.l + W && Y >= M.t && Y <= M.t + H;
|
|
2083
|
+
const halo = ' paint-order="stroke" stroke="' + T.surface + '" stroke-width="3" stroke-linejoin="round"';
|
|
2084
|
+
// The name sits right of the bubble's current position; years keep clear.
|
|
2085
|
+
const L = t.L, i = t.obj;
|
|
2086
|
+
let nameBox = null, nameSvg = '';
|
|
2087
|
+
if (t.name && L._x && L._y) {
|
|
2088
|
+
const X = M.l + screenX(L._x[i] * ext[0]), Y = M.t + screenY(L._y[i] * ext[1]);
|
|
2089
|
+
if (inside(X, Y)) {
|
|
2090
|
+
const x0 = X + (L._size ? L._size[i] * 0.5 : 4) + 4;
|
|
2091
|
+
nameBox = [x0 - 4, Y - 9, x0 + 7.2 * t.name.length + 4, Y + 9];
|
|
2092
|
+
nameSvg = '<text x="' + x0 + '" y="' + Y + '" fill="' + t.color +
|
|
2093
|
+
'" font-size="12" font-weight="700" dominant-baseline="middle"' + halo + '>' +
|
|
2094
|
+
plot3Esc(t.name) + '</text>';
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
const underName = (X, Y) => nameBox && X + 5 + 26 > nameBox[0] && X + 5 < nameBox[2] &&
|
|
2098
|
+
Y - 5 > nameBox[1] - 6 && Y - 5 - 10 < nameBox[3];
|
|
2099
|
+
const kept = [];
|
|
2100
|
+
t.pts.forEach((p, k) => {
|
|
2101
|
+
const X = M.l + screenX(p.x * ext[0]), Y = M.t + screenY(p.y * ext[1]);
|
|
2102
|
+
const label = trailYear(p.f);
|
|
2103
|
+
if (!label || !inside(X, Y) || underName(X, Y)) return;
|
|
2104
|
+
const prev = kept[kept.length - 1];
|
|
2105
|
+
const near = prev && Math.hypot(X - prev.X, Y - prev.Y) < 34;
|
|
2106
|
+
if (near && k !== t.pts.length - 1) return;
|
|
2107
|
+
if (near && kept.length > 1) kept.pop(); // the last year wins a crowded end
|
|
2108
|
+
else if (near) return;
|
|
2109
|
+
kept.push({ X, Y, label });
|
|
2110
|
+
});
|
|
2111
|
+
return kept.map(k => '<text x="' + (k.X + 5) + '" y="' + (k.Y - 5) + '" fill="' + T.muted +
|
|
2112
|
+
'" font-size="10"' + halo + '>' + plot3Esc(k.label) + '</text>').join('') + nameSvg;
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2036
2115
|
function layout() {
|
|
2037
2116
|
placeLegend();
|
|
2038
2117
|
W = Math.max(50, figEl.clientWidth - M.l - M.r);
|
|
2039
|
-
H = Math.max(50, figEl.clientHeight - M.t - M.b);
|
|
2118
|
+
H = Math.max(50, figEl.clientHeight - M.t - M.b - legendReserve);
|
|
2040
2119
|
if (equalAspect) applyEqual();
|
|
2041
2120
|
host.style.left = M.l + 'px'; host.style.top = M.t + 'px';
|
|
2042
2121
|
renderer.setSize(W, H);
|
|
@@ -2274,7 +2353,9 @@ if (!S.is3d) {
|
|
|
2274
2353
|
: head
|
|
2275
2354
|
+ fmtSpan('x', xv, xSpan) + ', '
|
|
2276
2355
|
+ fmtSpan('y', yv, ySpan) + sizeBit;
|
|
2277
|
-
|
|
2356
|
+
const moving = L.kind === 'point' && L.frames && S.transition;
|
|
2357
|
+
const cr = clickRadius(L, i, 6);
|
|
2358
|
+
consider(d, sx, sy, html + (moving && d <= cr * cr ? trailHint(L, i) : ''), !!moving);
|
|
2278
2359
|
}
|
|
2279
2360
|
}
|
|
2280
2361
|
}
|
|
@@ -2302,14 +2383,14 @@ if (!S.is3d) {
|
|
|
2302
2383
|
for (let i = 0; i < L.n; i++) {
|
|
2303
2384
|
if (L._alpha && L._alpha[i] <= 0.04) continue;
|
|
2304
2385
|
const sx = screenX(L._x[i]), sy = screenY(L._y[i]);
|
|
2305
|
-
const rad = L
|
|
2386
|
+
const rad = clickRadius(L, i, 6);
|
|
2306
2387
|
const d = (sx - mx) * (sx - mx) + (sy - my) * (sy - my);
|
|
2307
2388
|
if (d <= rad * rad && d < hitD) { hitD = d; hit = { L, i }; }
|
|
2308
2389
|
}
|
|
2309
2390
|
}
|
|
2391
|
+
tip.style.display = 'none';
|
|
2310
2392
|
if (!hit) { clearTrail(); draw(); return; }
|
|
2311
|
-
|
|
2312
|
-
if (trailId === key) { clearTrail(); draw(); return; }
|
|
2393
|
+
if (trailId === trailKey(hit.L, hit.i)) { clearTrail(); draw(); return; }
|
|
2313
2394
|
setTrail(hit.L, hit.i);
|
|
2314
2395
|
draw();
|
|
2315
2396
|
});
|
|
@@ -2651,6 +2732,7 @@ if (!S.is3d) {
|
|
|
2651
2732
|
}
|
|
2652
2733
|
}
|
|
2653
2734
|
hoverHold = !!(best && best[0].kind === 'point' && best[0].frames && S.transition);
|
|
2735
|
+
const hoverD = bestD;
|
|
2654
2736
|
if (!best) { tip3.style.display = 'none'; return; }
|
|
2655
2737
|
const [L, i, sx, sy, xn, yn, zn] = best;
|
|
2656
2738
|
const xv = fromScale('x', xn);
|
|
@@ -2678,6 +2760,10 @@ if (!S.is3d) {
|
|
|
2678
2760
|
+ axisPair('y', fmt(yv)) + ', '
|
|
2679
2761
|
+ axisPair('z', fmt(zv)) + sizeBit
|
|
2680
2762
|
: head + fmt(xv) + ', ' + fmt(yv) + ', ' + fmt(zv) + sizeBit;
|
|
2763
|
+
if (hoverHold) {
|
|
2764
|
+
const cr = sizeAtten ? 8 : clickRadius(L, i, 8);
|
|
2765
|
+
if (hoverD <= cr * cr) tip3.innerHTML += trailHint(L, i);
|
|
2766
|
+
}
|
|
2681
2767
|
plot3Typeset(tip3);
|
|
2682
2768
|
tip3.style.left = Math.min(w - 8, sx + 12) + 'px';
|
|
2683
2769
|
tip3.style.top = Math.max(8, sy - 10) + 'px';
|
|
@@ -2728,20 +2814,41 @@ if (!S.is3d) {
|
|
|
2728
2814
|
v.project(cam);
|
|
2729
2815
|
const sx = (v.x * 0.5 + 0.5) * w;
|
|
2730
2816
|
const sy = (-v.y * 0.5 + 0.5) * h;
|
|
2731
|
-
const rad =
|
|
2817
|
+
const rad = sizeAtten ? 8 : clickRadius(L, i, 8);
|
|
2732
2818
|
const d = (sx - mx) * (sx - mx) + (sy - my) * (sy - my);
|
|
2733
2819
|
if (d <= rad * rad && d < hitD) { hitD = d; hit = { L, i }; }
|
|
2734
2820
|
}
|
|
2735
2821
|
}
|
|
2822
|
+
tip3.style.display = 'none';
|
|
2736
2823
|
if (!hit) { clearTrail(); return; }
|
|
2737
|
-
|
|
2738
|
-
if (trailId === key) { clearTrail(); return; }
|
|
2824
|
+
if (trailId === trailKey(hit.L, hit.i)) { clearTrail(); return; }
|
|
2739
2825
|
setTrail(hit.L, hit.i);
|
|
2826
|
+
// The scene cannot move text every frame cheaply: label the path's
|
|
2827
|
+
// ends, the first year, and the name with the last year.
|
|
2828
|
+
if (trail && trailInfo && trailInfo.pts.length) {
|
|
2829
|
+
const t = trailInfo;
|
|
2830
|
+
const at = p => new THREE.Vector3(p.x * t.ext[0], p.y * t.ext[1], p.z * t.ext[2]);
|
|
2831
|
+
const first = t.pts[0], last = t.pts[t.pts.length - 1];
|
|
2832
|
+
const startText = trailYear(first.f);
|
|
2833
|
+
if (startText) {
|
|
2834
|
+
const sp = sprite(startText, true);
|
|
2835
|
+
sp.center.set(-0.1, -0.2);
|
|
2836
|
+
sp.position.copy(at(first));
|
|
2837
|
+
trail.add(sp);
|
|
2838
|
+
}
|
|
2839
|
+
const endText = [t.name, trailYear(last.f)].filter(Boolean).join(' ');
|
|
2840
|
+
if (endText) {
|
|
2841
|
+
const sp = sprite(endText, false);
|
|
2842
|
+
sp.center.set(-0.1, -0.2);
|
|
2843
|
+
sp.position.copy(at(last));
|
|
2844
|
+
trail.add(sp);
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2740
2847
|
});
|
|
2741
2848
|
|
|
2742
2849
|
function layout() {
|
|
2743
2850
|
placeLegend();
|
|
2744
|
-
const w = Math.max(figEl.clientWidth, 1), h = Math.max(figEl.clientHeight, 1);
|
|
2851
|
+
const w = Math.max(figEl.clientWidth, 1), h = Math.max(figEl.clientHeight - legendReserve, 1);
|
|
2745
2852
|
renderer.setSize(w, h);
|
|
2746
2853
|
cam.aspect = w / h;
|
|
2747
2854
|
cam.updateProjectionMatrix();
|
|
@@ -2763,7 +2870,7 @@ if (!S.is3d) {
|
|
|
2763
2870
|
const touches = (a, b, air) => a[0] - air < b[0] + b[2] && b[0] - air < a[0] + a[2] &&
|
|
2764
2871
|
a[1] - air < b[1] + b[3] && b[1] - air < a[1] + a[3];
|
|
2765
2872
|
function declutterTicks() {
|
|
2766
|
-
const w = Math.max(figEl.clientWidth, 1), h = Math.max(figEl.clientHeight, 1);
|
|
2873
|
+
const w = Math.max(figEl.clientWidth, 1), h = Math.max(figEl.clientHeight - legendReserve, 1);
|
|
2767
2874
|
const placed = [];
|
|
2768
2875
|
for (const marks of tickSprites) {
|
|
2769
2876
|
if (!marks.length) continue;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plot3
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.5
|
|
4
4
|
Summary: ggplot2 grammar of graphics for Python: interactive WebGL figures and journal-ready PNG, SVG, and PDF
|
|
5
5
|
Author: Rigoberto Leyva Salmeron
|
|
6
6
|
License-Expression: MIT
|
|
@@ -130,6 +130,10 @@ gapminder = pd.read_csv("https://raw.githubusercontent.com/kirenz/datasets/maste
|
|
|
130
130
|
`{frame_time}` in the title shows the current year as it plays.
|
|
131
131
|
`transition_states("phase")` steps through categories instead of time.
|
|
132
132
|
|
|
133
|
+
Click a bubble to trace it: its path through every frame appears, with
|
|
134
|
+
the years along it and its name (from `group=`) riding beside it as it
|
|
135
|
+
plays. Click it again, or empty space, to clear it. This works in 3D too.
|
|
136
|
+
|
|
133
137
|
<details>
|
|
134
138
|
<summary>Run this without downloading anything (made-up gapminder-shaped data)</summary>
|
|
135
139
|
|
plot3-0.6.3/plot3/__version__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.6.3"
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|