mirror of
https://github.com/apache/superset.git
synced 2026-04-22 01:24:43 +00:00
refactor(monorepo): move superset-ui to superset(stage 2) (#17552)
This commit is contained in:
@@ -0,0 +1,280 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
/* eslint-disable react/require-default-props */
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { CanvasOverlay } from 'react-map-gl';
|
||||
import { kmToPixels, MILES_PER_KM } from './utils/geo';
|
||||
import roundDecimal from './utils/roundDecimal';
|
||||
import luminanceFromRGB from './utils/luminanceFromRGB';
|
||||
import 'mapbox-gl/dist/mapbox-gl.css';
|
||||
|
||||
const propTypes = {
|
||||
aggregation: PropTypes.string,
|
||||
compositeOperation: PropTypes.string,
|
||||
dotRadius: PropTypes.number,
|
||||
lngLatAccessor: PropTypes.func,
|
||||
locations: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
pointRadiusUnit: PropTypes.string,
|
||||
renderWhileDragging: PropTypes.bool,
|
||||
rgb: PropTypes.arrayOf(
|
||||
PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
),
|
||||
zoom: PropTypes.number,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
// Same as browser default.
|
||||
compositeOperation: 'source-over',
|
||||
dotRadius: 4,
|
||||
lngLatAccessor: location => [location[0], location[1]],
|
||||
renderWhileDragging: true,
|
||||
};
|
||||
|
||||
const computeClusterLabel = (properties, aggregation) => {
|
||||
const count = properties.point_count;
|
||||
if (!aggregation) {
|
||||
return count;
|
||||
}
|
||||
if (aggregation === 'sum' || aggregation === 'min' || aggregation === 'max') {
|
||||
return properties[aggregation];
|
||||
}
|
||||
const { sum } = properties;
|
||||
const mean = sum / count;
|
||||
if (aggregation === 'mean') {
|
||||
return Math.round(100 * mean) / 100;
|
||||
}
|
||||
const { squaredSum } = properties;
|
||||
const variance = squaredSum / count - (sum / count) ** 2;
|
||||
if (aggregation === 'var') {
|
||||
return Math.round(100 * variance) / 100;
|
||||
}
|
||||
if (aggregation === 'stdev') {
|
||||
return Math.round(100 * Math.sqrt(variance)) / 100;
|
||||
}
|
||||
|
||||
// fallback to point_count, this really shouldn't happen
|
||||
return count;
|
||||
};
|
||||
|
||||
class ScatterPlotGlowOverlay extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.redraw = this.redraw.bind(this);
|
||||
}
|
||||
|
||||
drawText(ctx, pixel, options = {}) {
|
||||
const IS_DARK_THRESHOLD = 110;
|
||||
const {
|
||||
fontHeight = 0,
|
||||
label = '',
|
||||
radius = 0,
|
||||
rgb = [0, 0, 0],
|
||||
shadow = false,
|
||||
} = options;
|
||||
const maxWidth = radius * 1.8;
|
||||
const luminance = luminanceFromRGB(rgb[1], rgb[2], rgb[3]);
|
||||
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
ctx.fillStyle = luminance <= IS_DARK_THRESHOLD ? 'white' : 'black';
|
||||
ctx.font = `${fontHeight}px sans-serif`;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
if (shadow) {
|
||||
ctx.shadowBlur = 15;
|
||||
ctx.shadowColor = luminance <= IS_DARK_THRESHOLD ? 'black' : '';
|
||||
}
|
||||
|
||||
const textWidth = ctx.measureText(label).width;
|
||||
if (textWidth > maxWidth) {
|
||||
const scale = fontHeight / textWidth;
|
||||
ctx.font = `${scale * maxWidth}px sans-serif`;
|
||||
}
|
||||
|
||||
const { compositeOperation } = this.props;
|
||||
|
||||
ctx.fillText(label, pixel[0], pixel[1]);
|
||||
ctx.globalCompositeOperation = compositeOperation;
|
||||
ctx.shadowBlur = 0;
|
||||
ctx.shadowColor = '';
|
||||
}
|
||||
|
||||
// Modified: https://github.com/uber/react-map-gl/blob/master/overlays/scatterplot.react.js
|
||||
redraw({ width, height, ctx, isDragging, project }) {
|
||||
const {
|
||||
aggregation,
|
||||
compositeOperation,
|
||||
dotRadius,
|
||||
lngLatAccessor,
|
||||
locations,
|
||||
pointRadiusUnit,
|
||||
renderWhileDragging,
|
||||
rgb,
|
||||
zoom,
|
||||
} = this.props;
|
||||
|
||||
const radius = dotRadius;
|
||||
const clusterLabelMap = [];
|
||||
|
||||
locations.forEach((location, i) => {
|
||||
if (location.properties.cluster) {
|
||||
clusterLabelMap[i] = computeClusterLabel(
|
||||
location.properties,
|
||||
aggregation,
|
||||
);
|
||||
}
|
||||
}, this);
|
||||
|
||||
const maxLabel = Math.max(...clusterLabelMap.filter(v => !Number.isNaN(v)));
|
||||
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
ctx.globalCompositeOperation = compositeOperation;
|
||||
|
||||
if ((renderWhileDragging || !isDragging) && locations) {
|
||||
locations.forEach(function _forEach(location, i) {
|
||||
const pixel = project(lngLatAccessor(location));
|
||||
const pixelRounded = [
|
||||
roundDecimal(pixel[0], 1),
|
||||
roundDecimal(pixel[1], 1),
|
||||
];
|
||||
|
||||
if (
|
||||
pixelRounded[0] + radius >= 0 &&
|
||||
pixelRounded[0] - radius < width &&
|
||||
pixelRounded[1] + radius >= 0 &&
|
||||
pixelRounded[1] - radius < height
|
||||
) {
|
||||
ctx.beginPath();
|
||||
if (location.properties.cluster) {
|
||||
let clusterLabel = clusterLabelMap[i];
|
||||
const scaledRadius = roundDecimal(
|
||||
(clusterLabel / maxLabel) ** 0.5 * radius,
|
||||
1,
|
||||
);
|
||||
const fontHeight = roundDecimal(scaledRadius * 0.5, 1);
|
||||
const [x, y] = pixelRounded;
|
||||
const gradient = ctx.createRadialGradient(
|
||||
x,
|
||||
y,
|
||||
scaledRadius,
|
||||
x,
|
||||
y,
|
||||
0,
|
||||
);
|
||||
|
||||
gradient.addColorStop(
|
||||
1,
|
||||
`rgba(${rgb[1]}, ${rgb[2]}, ${rgb[3]}, 0.8)`,
|
||||
);
|
||||
gradient.addColorStop(
|
||||
0,
|
||||
`rgba(${rgb[1]}, ${rgb[2]}, ${rgb[3]}, 0)`,
|
||||
);
|
||||
ctx.arc(
|
||||
pixelRounded[0],
|
||||
pixelRounded[1],
|
||||
scaledRadius,
|
||||
0,
|
||||
Math.PI * 2,
|
||||
);
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.fill();
|
||||
|
||||
if (Number.isFinite(parseFloat(clusterLabel))) {
|
||||
if (clusterLabel >= 10000) {
|
||||
clusterLabel = `${Math.round(clusterLabel / 1000)}k`;
|
||||
} else if (clusterLabel >= 1000) {
|
||||
clusterLabel = `${Math.round(clusterLabel / 100) / 10}k`;
|
||||
}
|
||||
this.drawText(ctx, pixelRounded, {
|
||||
fontHeight,
|
||||
label: clusterLabel,
|
||||
radius: scaledRadius,
|
||||
rgb,
|
||||
shadow: true,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const defaultRadius = radius / 6;
|
||||
const radiusProperty = location.properties.radius;
|
||||
const pointMetric = location.properties.metric;
|
||||
let pointRadius =
|
||||
radiusProperty === null ? defaultRadius : radiusProperty;
|
||||
let pointLabel;
|
||||
|
||||
if (radiusProperty !== null) {
|
||||
const pointLatitude = lngLatAccessor(location)[1];
|
||||
if (pointRadiusUnit === 'Kilometers') {
|
||||
pointLabel = `${roundDecimal(pointRadius, 2)}km`;
|
||||
pointRadius = kmToPixels(pointRadius, pointLatitude, zoom);
|
||||
} else if (pointRadiusUnit === 'Miles') {
|
||||
pointLabel = `${roundDecimal(pointRadius, 2)}mi`;
|
||||
pointRadius = kmToPixels(
|
||||
pointRadius * MILES_PER_KM,
|
||||
pointLatitude,
|
||||
zoom,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (pointMetric !== null) {
|
||||
pointLabel = Number.isFinite(parseFloat(pointMetric))
|
||||
? roundDecimal(pointMetric, 2)
|
||||
: pointMetric;
|
||||
}
|
||||
|
||||
// Fall back to default points if pointRadius wasn't a numerical column
|
||||
if (!pointRadius) {
|
||||
pointRadius = defaultRadius;
|
||||
}
|
||||
|
||||
ctx.arc(
|
||||
pixelRounded[0],
|
||||
pixelRounded[1],
|
||||
roundDecimal(pointRadius, 1),
|
||||
0,
|
||||
Math.PI * 2,
|
||||
);
|
||||
ctx.fillStyle = `rgb(${rgb[1]}, ${rgb[2]}, ${rgb[3]})`;
|
||||
ctx.fill();
|
||||
|
||||
if (pointLabel !== undefined) {
|
||||
this.drawText(ctx, pixelRounded, {
|
||||
fontHeight: roundDecimal(pointRadius, 1),
|
||||
label: pointLabel,
|
||||
radius: pointRadius,
|
||||
rgb,
|
||||
shadow: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return <CanvasOverlay redraw={this.redraw} />;
|
||||
}
|
||||
}
|
||||
|
||||
ScatterPlotGlowOverlay.propTypes = propTypes;
|
||||
ScatterPlotGlowOverlay.defaultProps = defaultProps;
|
||||
|
||||
export default ScatterPlotGlowOverlay;
|
||||
Reference in New Issue
Block a user