Skip to content

Commit d4e810a

Browse files
committed
Enforce canvas width & height to be at least 1px
Also skip tests relying on online images when one is offline
1 parent c0bb367 commit d4e810a

5 files changed

Lines changed: 127 additions & 62 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
## Next
22

3+
## v1.4.2
4+
5+
- Enforce the canvas width and height to be at least 1px to prevent view operations from breaking.
6+
37
## v1.4.1
48

59
- Bump `dom-2d-camera` to publish view updates on camera tweens ([#95](https://github.com/flekschas/regl-scatterplot/issues/95))

src/constants.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ export const DEFAULT_VIEW = new Float32Array([
131131
0, 0, 0, 1
132132
]);
133133

134+
// Error codes
135+
export const IMAGE_LOAD_ERROR = 'IMAGE_LOAD_ERROR';
136+
134137
// Default misc
135138
export const DEFAULT_BACKGROUND_IMAGE = null;
136139
export const DEFAULT_SHOW_RETICLE = false;
@@ -146,3 +149,4 @@ export const SINGLE_CLICK_DELAY = 200;
146149
export const LONG_CLICK_TIME = 500;
147150
export const Z_NAMES = new Set(['z', 'valueZ', 'valueA', 'value1', 'category']);
148151
export const W_NAMES = new Set(['w', 'valueW', 'valueB', 'value2', 'value']);
152+
export const DEFAULT_IMAGE_LOAD_TIMEOUT = 15000;

src/index.js

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ import {
104104
DEFAULT_OPACITY_INACTIVE_SCALE,
105105
Z_NAMES,
106106
W_NAMES,
107+
DEFAULT_IMAGE_LOAD_TIMEOUT,
107108
} from './constants';
108109

109110
import {
@@ -1028,7 +1029,7 @@ const createScatterplot = (
10281029
};
10291030

10301031
const setCurrentHeight = (newCurrentHeight) => {
1031-
currentHeight = newCurrentHeight;
1032+
currentHeight = Math.max(1, newCurrentHeight);
10321033
canvas.height = Math.floor(currentHeight * window.devicePixelRatio);
10331034
if (yScale) {
10341035
yScale.range([currentHeight, 0]);
@@ -1085,7 +1086,7 @@ const createScatterplot = (
10851086
};
10861087

10871088
const setCurrentWidth = (newCurrentWidth) => {
1088-
currentWidth = newCurrentWidth;
1089+
currentWidth = Math.max(1, newCurrentWidth);
10891090
canvas.width = Math.floor(currentWidth * window.devicePixelRatio);
10901091
if (xScale) {
10911092
xScale.range([0, currentWidth]);
@@ -2205,13 +2206,16 @@ const createScatterplot = (
22052206
if (!newBackgroundImage) {
22062207
backgroundImage = null;
22072208
} else if (isString(newBackgroundImage)) {
2208-
createTextureFromUrl(renderer.regl, newBackgroundImage).then(
2209-
(texture) => {
2209+
createTextureFromUrl(renderer.regl, newBackgroundImage)
2210+
.then((texture) => {
22102211
backgroundImage = texture;
22112212
draw = true;
22122213
pubSub.publish('backgroundImageReady');
2213-
}
2214-
);
2214+
})
2215+
.catch(() => {
2216+
console.error(`Count not create texture from ${newBackgroundImage}`);
2217+
backgroundImage = null;
2218+
});
22152219
// eslint-disable-next-line no-underscore-dangle
22162220
} else if (newBackgroundImage._reglType === 'texture2d') {
22172221
backgroundImage = newBackgroundImage;
@@ -3170,8 +3174,10 @@ const createScatterplot = (
31703174
return renderer.isSupported;
31713175
},
31723176
clear: withDraw(clear),
3173-
createTextureFromUrl: (/** @type {string} */ url) =>
3174-
createTextureFromUrl(renderer.regl, url),
3177+
createTextureFromUrl: (
3178+
/** @type {string} */ url,
3179+
/** @type {number} */ timeout = DEFAULT_IMAGE_LOAD_TIMEOUT
3180+
) => createTextureFromUrl(renderer.regl, url, timeout),
31753181
deselect,
31763182
destroy,
31773183
draw: publicDraw,

src/utils.js

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import createOriginalRegl from 'regl';
22

3-
import { GL_EXTENSIONS } from './constants';
3+
import {
4+
GL_EXTENSIONS,
5+
DEFAULT_IMAGE_LOAD_TIMEOUT,
6+
IMAGE_LOAD_ERROR,
7+
} from './constants';
48

59
/**
610
* Get the max value of an array. helper method to be used with `Array.reduce()`.
@@ -130,17 +134,23 @@ export const limit = (choices, defaultChoice) => (choice) =>
130134
* @param {boolean} isCrossOrigin If `true` allow loading image from a source of another origin.
131135
* @return {Promise<HTMLImageElement>} Promise resolving to the image once its loaded
132136
*/
133-
export const loadImage = (src, isCrossOrigin = false) =>
134-
new Promise((accept, reject) => {
137+
export const loadImage = (
138+
src,
139+
isCrossOrigin = false,
140+
timeout = DEFAULT_IMAGE_LOAD_TIMEOUT
141+
) =>
142+
new Promise((resolve, reject) => {
135143
const image = new Image();
136144
if (isCrossOrigin) image.crossOrigin = 'anonymous';
137145
image.src = src;
138146
image.onload = () => {
139-
accept(image);
147+
resolve(image);
140148
};
141-
image.onerror = (error) => {
142-
reject(error);
149+
const rejectPromise = () => {
150+
reject(new Error(IMAGE_LOAD_ERROR));
143151
};
152+
image.onerror = rejectPromise;
153+
setTimeout(rejectPromise, timeout);
144154
});
145155

146156
/**
@@ -151,11 +161,16 @@ export const loadImage = (src, isCrossOrigin = false) =>
151161
* @param {string} url Source URL of the image.
152162
* @return {Promise<import('regl').Texture2D>} Promise resolving to the texture object.
153163
*/
154-
export const createTextureFromUrl = (regl, url) =>
164+
export const createTextureFromUrl = (
165+
regl,
166+
url,
167+
timeout = DEFAULT_IMAGE_LOAD_TIMEOUT
168+
) =>
155169
new Promise((resolve, reject) => {
156170
loadImage(
157171
url,
158-
url.indexOf(window.location.origin) !== 0 && url.indexOf('base64') === -1
172+
url.indexOf(window.location.origin) !== 0 && url.indexOf('base64') === -1,
173+
timeout
159174
)
160175
.then((image) => {
161176
resolve(regl.texture(image));

tests/index.js

Lines changed: 82 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ import {
4242
KEY_ACTION_ROTATE,
4343
SINGLE_CLICK_DELAY,
4444
DEFAULT_OPACITY,
45+
DEFAULT_IMAGE_LOAD_TIMEOUT,
46+
IMAGE_LOAD_ERROR,
4547
} from '../src/constants';
4648

4749
import { toRgba, isNormFloatArray } from '../src/utils';
@@ -196,17 +198,25 @@ test('createScatterplot({ cameraTarget, cameraDistance, cameraRotation, cameraVi
196198
test('createTextureFromUrl()', async (t) => {
197199
const regl = createRegl(createCanvas());
198200

199-
const texture = await createTextureFromUrl(
200-
regl,
201-
'https://picsum.photos/300/200/',
202-
true
203-
);
201+
try {
202+
const texture = await createTextureFromUrl(
203+
regl,
204+
'https://picsum.photos/300/200/',
205+
true
206+
);
204207

205-
t.equal(
206-
texture._reglType, // eslint-disable-line no-underscore-dangle
207-
'texture2d',
208-
'texture should be a Regl texture object'
209-
);
208+
t.equal(
209+
texture._reglType, // eslint-disable-line no-underscore-dangle
210+
'texture2d',
211+
'texture should be a Regl texture object'
212+
);
213+
} catch (e) {
214+
if (e.message === IMAGE_LOAD_ERROR) {
215+
t.skip('Skipping because image loading timed out');
216+
} else {
217+
t.fail('Failed to load image from URL');
218+
}
219+
}
210220
});
211221

212222
test('createRenderer()', (t) => {
@@ -362,50 +372,76 @@ test('set({ backgroundImage })', async (t) => {
362372
const regl = createRegl(canvas);
363373
const scatterplot = createScatterplot({ canvas, regl });
364374

365-
let backgroundImage = await createTextureFromUrl(
366-
regl,
367-
'https://picsum.photos/300/200/'
368-
);
369-
370-
scatterplot.set({ backgroundImage });
371-
372-
t.equal(
373-
scatterplot.get('backgroundImage'),
374-
backgroundImage,
375-
'background image should be a Regl texture'
376-
);
375+
try {
376+
const backgroundImage = await createTextureFromUrl(
377+
regl,
378+
'https://picsum.photos/300/200/'
379+
);
377380

378-
backgroundImage = await scatterplot.createTextureFromUrl(
379-
'https://picsum.photos/300/200/'
380-
);
381+
scatterplot.set({ backgroundImage });
381382

382-
scatterplot.set({ backgroundImage });
383+
t.equal(
384+
scatterplot.get('backgroundImage'),
385+
backgroundImage,
386+
'background image should be a Regl texture'
387+
);
388+
} catch (e) {
389+
if (e.message === IMAGE_LOAD_ERROR) {
390+
t.skip(`Failed to load image from URL: ${e.message}`);
391+
} else {
392+
t.fail('Could not create background image from URL');
393+
}
394+
}
383395

384-
t.equal(
385-
scatterplot.get('backgroundImage'),
386-
backgroundImage,
387-
'background image should be a Regl texture'
388-
);
396+
try {
397+
const backgroundImage = await scatterplot.createTextureFromUrl(
398+
'https://picsum.photos/300/200/'
399+
);
389400

390-
scatterplot.set({ backgroundImage: null });
401+
scatterplot.set({ backgroundImage });
391402

392-
t.equal(
393-
scatterplot.get('backgroundImage'),
394-
null,
395-
'background image should be nullifyable'
396-
);
403+
t.equal(
404+
scatterplot.get('backgroundImage'),
405+
backgroundImage,
406+
'background image should be a Regl texture'
407+
);
397408

398-
scatterplot.set({ backgroundImage: 'https://picsum.photos/300/200/' });
409+
scatterplot.set({ backgroundImage: null });
399410

400-
await new Promise((resolve) => {
401-
scatterplot.subscribe('backgroundImageReady', resolve, 1);
402-
});
411+
t.equal(
412+
scatterplot.get('backgroundImage'),
413+
null,
414+
'background image should be nullifyable'
415+
);
416+
} catch (e) {
417+
if (e.message === IMAGE_LOAD_ERROR) {
418+
t.skip(`Failed to load image from URL: ${e.message}`);
419+
} else {
420+
t.fail('Could not create background image from URL');
421+
}
422+
}
423+
424+
try {
425+
await new Promise((resolve, reject) => {
426+
scatterplot.subscribe('backgroundImageReady', resolve, 1);
427+
scatterplot.set({ backgroundImage: 'https://picsum.photos/300/200/' });
428+
setTimeout(() => {
429+
reject(new Error(IMAGE_LOAD_ERROR));
430+
}, DEFAULT_IMAGE_LOAD_TIMEOUT);
431+
});
403432

404-
t.equal(
405-
scatterplot.get('backgroundImage').width,
406-
300,
407-
'background image should be loaded by scatterplot'
408-
);
433+
t.equal(
434+
scatterplot.get('backgroundImage').width,
435+
300,
436+
'background image should be loaded by scatterplot'
437+
);
438+
} catch (e) {
439+
if (e.message === IMAGE_LOAD_ERROR) {
440+
t.skip(`Failed to load image from URL: ${e.message}`);
441+
} else {
442+
t.fail('Could not create background image from URL');
443+
}
444+
}
409445

410446
// Base64 image
411447
scatterplot.set({

0 commit comments

Comments
 (0)