Skip to content

Commit ca628b5

Browse files
authored
[Chore] rewrite plot container for perf improvement (#3133)
* rewrite plot container for perf improvement * modify preview image to apply max dimension and maintain aspect ratio * converting PlotContainer to FC with hooks/memoization * Added an equality check on datauri in setExportImageDataUriUpdater to skip unnecessary state updates. * Enhanced mapStateSelector and plotContainerSelector with createSelector * broke out export-image settings into individual props. --------- Signed-off-by: Shan He <heshan0131@gmail.com>
1 parent 49f4c3d commit ca628b5

14 files changed

Lines changed: 374 additions & 325 deletions

File tree

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,28 @@
11
// SPDX-License-Identifier: MIT
22
// Copyright contributors to the kepler.gl project
33

4-
import js from '@eslint/js'
5-
import globals from 'globals'
6-
import reactHooks from 'eslint-plugin-react-hooks'
7-
import reactRefresh from 'eslint-plugin-react-refresh'
8-
import tseslint from 'typescript-eslint'
4+
import js from '@eslint/js';
5+
import globals from 'globals';
6+
import reactHooks from 'eslint-plugin-react-hooks';
7+
import reactRefresh from 'eslint-plugin-react-refresh';
8+
import tseslint from 'typescript-eslint';
99

1010
export default tseslint.config(
11-
{ ignores: ['dist'] },
11+
{ignores: ['dist']},
1212
{
1313
extends: [js.configs.recommended, ...tseslint.configs.recommended],
1414
files: ['**/*.{ts,tsx}'],
1515
languageOptions: {
1616
ecmaVersion: 2020,
17-
globals: globals.browser,
17+
globals: globals.browser
1818
},
1919
plugins: {
2020
'react-hooks': reactHooks,
21-
'react-refresh': reactRefresh,
21+
'react-refresh': reactRefresh
2222
},
2323
rules: {
2424
...reactHooks.configs.recommended.rules,
25-
'react-refresh/only-export-components': [
26-
'warn',
27-
{ allowConstantExport: true },
28-
],
29-
},
30-
},
31-
)
25+
'react-refresh/only-export-components': ['warn', {allowConstantExport: true}]
26+
}
27+
}
28+
);
Lines changed: 66 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,113 +1,95 @@
11
// SPDX-License-Identifier: MIT
22
// Copyright contributors to the kepler.gl project
33

4-
import { defineConfig } from "vite";
5-
import react from "@vitejs/plugin-react";
6-
import wasm from "vite-plugin-wasm";
7-
import { resolve } from "path";
4+
import {defineConfig} from 'vite';
5+
import react from '@vitejs/plugin-react';
6+
import wasm from 'vite-plugin-wasm';
7+
import {resolve} from 'path';
88

99
// https://vitejs.dev/config/
1010
export default defineConfig({
1111
plugins: [wasm(), react()],
1212
server: {
1313
port: 8081,
14-
open: true,
14+
open: true
1515
},
1616
build: {
17-
outDir: "dist",
17+
outDir: 'dist',
1818
sourcemap: false,
1919
minify: true,
2020
rollupOptions: {
2121
input: {
22-
main: resolve(__dirname, "index.html"),
23-
},
22+
main: resolve(__dirname, 'index.html')
23+
}
2424
},
25-
target: "esnext",
25+
target: 'esnext',
2626
commonjsOptions: {
2727
include: [/node_modules/],
28-
transformMixedEsModules: true,
29-
},
28+
transformMixedEsModules: true
29+
}
3030
},
3131
define: {
32-
"process.env.NODE_ENV": JSON.stringify(
33-
process.env.NODE_ENV || "production"
34-
),
35-
"process.env.MapboxAccessToken": JSON.stringify(
36-
process.env.MapboxAccessToken || ""
37-
),
38-
"process.env.DropboxClientId": JSON.stringify(
39-
process.env.DropboxClientId || ""
40-
),
41-
"process.env.MapboxExportToken": JSON.stringify(
42-
process.env.MapboxExportToken || ""
43-
),
44-
"process.env.CartoClientId": JSON.stringify(
45-
process.env.CartoClientId || ""
46-
),
47-
"process.env.FoursquareClientId": JSON.stringify(
48-
process.env.FoursquareClientId || ""
49-
),
50-
"process.env.FoursquareDomain": JSON.stringify(
51-
process.env.FoursquareDomain || ""
52-
),
53-
"process.env.FoursquareAPIURL": JSON.stringify(
54-
process.env.FoursquareAPIURL || ""
55-
),
56-
"process.env.FoursquareUserMapsURL": JSON.stringify(
57-
process.env.FoursquareUserMapsURL || ""
58-
),
59-
"process.env.OpenAIToken": JSON.stringify(process.env.OpenAIToken || ""),
60-
"process.env.NODE_DEBUG": JSON.stringify(false),
32+
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production'),
33+
'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken || ''),
34+
'process.env.DropboxClientId': JSON.stringify(process.env.DropboxClientId || ''),
35+
'process.env.MapboxExportToken': JSON.stringify(process.env.MapboxExportToken || ''),
36+
'process.env.CartoClientId': JSON.stringify(process.env.CartoClientId || ''),
37+
'process.env.FoursquareClientId': JSON.stringify(process.env.FoursquareClientId || ''),
38+
'process.env.FoursquareDomain': JSON.stringify(process.env.FoursquareDomain || ''),
39+
'process.env.FoursquareAPIURL': JSON.stringify(process.env.FoursquareAPIURL || ''),
40+
'process.env.FoursquareUserMapsURL': JSON.stringify(process.env.FoursquareUserMapsURL || ''),
41+
'process.env.OpenAIToken': JSON.stringify(process.env.OpenAIToken || ''),
42+
'process.env.NODE_DEBUG': JSON.stringify(false)
6143
},
6244
resolve: {
63-
dedupe: ["styled-components"],
45+
dedupe: ['styled-components'],
6446
alias: {
65-
"@": resolve(__dirname, "./src"),
66-
},
47+
'@': resolve(__dirname, './src')
48+
}
6749
},
6850
optimizeDeps: {
69-
exclude: ["parquet-wasm", "@loaders.gl/parquet", "apache-arrow"],
51+
exclude: ['parquet-wasm', '@loaders.gl/parquet', 'apache-arrow'],
7052
include: [
71-
"buffer",
72-
"react",
73-
"react-dom",
74-
"react-redux",
75-
"redux",
76-
"styled-components",
77-
"@kepler.gl/components",
78-
"@kepler.gl/reducers",
79-
"@kepler.gl/actions",
80-
"@kepler.gl/constants",
81-
"@kepler.gl/utils",
82-
"@kepler.gl/schemas",
83-
"@kepler.gl/table",
84-
"@kepler.gl/layers",
85-
"@kepler.gl/deckgl-layers",
86-
"@kepler.gl/effects",
87-
"@kepler.gl/styles",
88-
"@kepler.gl/tasks",
89-
"@deck.gl/core",
90-
"@deck.gl/layers",
91-
"@deck.gl/aggregation-layers",
92-
"@deck.gl/geo-layers",
93-
"@deck.gl/mesh-layers",
94-
"@deck.gl/extensions",
95-
"@luma.gl/core",
96-
"@luma.gl/engine",
97-
"@luma.gl/gltools",
98-
"@luma.gl/shadertools",
99-
"@luma.gl/webgl",
100-
"@loaders.gl/core",
101-
"@loaders.gl/gltf",
102-
"@loaders.gl/images",
103-
"@loaders.gl/parquet",
104-
"@math.gl/core",
105-
"@math.gl/web-mercator",
106-
"gl-matrix",
107-
"lodash.uniq",
53+
'buffer',
54+
'react',
55+
'react-dom',
56+
'react-redux',
57+
'redux',
58+
'styled-components',
59+
'@kepler.gl/components',
60+
'@kepler.gl/reducers',
61+
'@kepler.gl/actions',
62+
'@kepler.gl/constants',
63+
'@kepler.gl/utils',
64+
'@kepler.gl/schemas',
65+
'@kepler.gl/table',
66+
'@kepler.gl/layers',
67+
'@kepler.gl/deckgl-layers',
68+
'@kepler.gl/effects',
69+
'@kepler.gl/styles',
70+
'@kepler.gl/tasks',
71+
'@deck.gl/core',
72+
'@deck.gl/layers',
73+
'@deck.gl/aggregation-layers',
74+
'@deck.gl/geo-layers',
75+
'@deck.gl/mesh-layers',
76+
'@deck.gl/extensions',
77+
'@luma.gl/core',
78+
'@luma.gl/engine',
79+
'@luma.gl/gltools',
80+
'@luma.gl/shadertools',
81+
'@luma.gl/webgl',
82+
'@loaders.gl/core',
83+
'@loaders.gl/gltf',
84+
'@loaders.gl/images',
85+
'@loaders.gl/parquet',
86+
'@math.gl/core',
87+
'@math.gl/web-mercator',
88+
'gl-matrix',
89+
'lodash.uniq'
10890
],
10991
esbuildOptions: {
110-
target: "es2020",
111-
},
112-
},
92+
target: 'es2020'
93+
}
94+
}
11395
});

src/actions/src/ui-state-actions.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33

44
import {createAction} from '@reduxjs/toolkit';
55
import {default as ActionTypes} from './action-types';
6-
import {Merge} from '@kepler.gl/types';
7-
import {ExportImage} from '@kepler.gl/constants';
6+
import {Merge, ExportImage} from '@kepler.gl/types';
87

98
/** TOGGLE_SIDE_PANEL */
109
export type ToggleSidePanelUpdaterAction = {

src/components/src/common/image-preview.tsx

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import React from 'react';
55
import styled from 'styled-components';
66
import LoadingSpinner from './loading-spinner';
7-
import {ExportImage} from '@kepler.gl/constants';
7+
import {ExportImage} from '@kepler.gl/types';
88

99
const StyledImagePreview = styled.div.attrs({
1010
className: 'image-preview'
@@ -14,7 +14,8 @@ const StyledImagePreview = styled.div.attrs({
1414
flex-direction: column;
1515
flex: 1;
1616
justify-content: center;
17-
padding: 30px;
17+
width: 100%;
18+
height: 100%;
1819
1920
.dimension,
2021
.instruction {
@@ -26,7 +27,17 @@ const StyledImagePreview = styled.div.attrs({
2627
border-radius: 4px;
2728
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.18);
2829
width: 100%;
30+
max-width: 400px;
2931
position: relative;
32+
overflow: hidden;
33+
}
34+
35+
.preview-image-container {
36+
position: relative;
37+
width: 100%;
38+
height: 0;
39+
padding-bottom: var(--aspect-ratio);
40+
max-height: 400px;
3041
}
3142
3243
.preview-image-placeholder {
@@ -35,6 +46,7 @@ const StyledImagePreview = styled.div.attrs({
3546
left: 0;
3647
width: 100%;
3748
height: 100%;
49+
object-fit: contain;
3850
}
3951
4052
.preview-image-spinner {
@@ -48,13 +60,21 @@ const StyledImagePreview = styled.div.attrs({
4860
padding: 12px;
4961
color: ${props => props.theme.errorColor};
5062
text-align: center;
63+
position: absolute;
64+
top: 0;
65+
left: 0;
66+
width: 100%;
67+
height: 100%;
68+
display: flex;
69+
align-items: center;
70+
justify-content: center;
5171
}
5272
`;
5373

5474
interface ImagePreviewProps {
5575
exportImage?: ExportImage;
5676
width?: number;
57-
showDimension?: false;
77+
showDimension?: boolean;
5878
}
5979

6080
/**
@@ -63,38 +83,38 @@ interface ImagePreviewProps {
6383
* @param {number} [props.width]
6484
* @param {boolean} [props.showDimension]
6585
*/
66-
const ImagePreview = ({exportImage, width = 400, showDimension = false}: ImagePreviewProps) => {
86+
const ImagePreview = ({exportImage, showDimension = false}: ImagePreviewProps) => {
6787
const {
6888
error,
6989
imageDataUri,
7090
processing,
7191
imageSize: {imageW = 0, imageH = 0} = {}
7292
} = exportImage || {};
7393

74-
const imageStyle = {
75-
width: `${width}px`,
76-
height: `${(imageH / (imageW || 1)) * width}px`
77-
};
94+
// Calculate aspect ratio percentage for padding-bottom trick
95+
const aspectRatio = imageW && imageH ? (imageH / imageW) * 100 : 75; // default to 4:3 if no dimensions
7896

7997
return (
80-
<StyledImagePreview>
98+
<StyledImagePreview style={{'--aspect-ratio': `${aspectRatio}%`} as React.CSSProperties}>
8199
{showDimension ? (
82100
<div className="dimension">
83101
{imageW} pixel x {imageH} pixel
84102
</div>
85103
) : null}
86-
<div className="preview-image" style={imageStyle}>
87-
{processing ? (
88-
<div className="preview-image-spinner">
89-
<LoadingSpinner />
90-
</div>
91-
) : error ? (
92-
<div className="preview-image--error">
93-
<span>{error.message || 'Generate map image failed!'}</span>
94-
</div>
95-
) : (
96-
<img className="preview-image-placeholder" src={imageDataUri} />
97-
)}
104+
<div className="preview-image">
105+
<div className="preview-image-container">
106+
{processing ? (
107+
<div className="preview-image-spinner">
108+
<LoadingSpinner />
109+
</div>
110+
) : error ? (
111+
<div className="preview-image--error">
112+
<span>{error.message || 'Generate map image failed!'}</span>
113+
</div>
114+
) : (
115+
<img className="preview-image-placeholder" src={imageDataUri} alt="Map preview" />
116+
)}
117+
</div>
98118
</div>
99119
</StyledImagePreview>
100120
);

0 commit comments

Comments
 (0)