mirror of
https://github.com/apache/superset.git
synced 2026-04-29 13:04:22 +00:00
Compare commits
20 Commits
fdf19db5e6
...
0.29.0rc7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32738aa796 | ||
|
|
3bede69740 | ||
|
|
a1048a0995 | ||
|
|
dac6219ef6 | ||
|
|
915466fdfc | ||
|
|
e48def35d6 | ||
|
|
6b0f42c81c | ||
|
|
4e8411c7f1 | ||
|
|
3c6f34cabd | ||
|
|
b9c8b0a112 | ||
|
|
9c88fefdeb | ||
|
|
6581a827ee | ||
|
|
7d28a84cce | ||
|
|
ba8f3c784c | ||
|
|
b23505e5c7 | ||
|
|
82a6708d10 | ||
|
|
88ab67b69a | ||
|
|
c08f4eaee1 | ||
|
|
1d74f308ee | ||
|
|
60cb608839 |
@@ -6,6 +6,8 @@ recursive-exclude superset/static/assets/docs *
|
||||
recursive-exclude superset/static/assets/images/viz_thumbnails_large *
|
||||
recursive-exclude superset/static/docs *
|
||||
recursive-exclude superset/static/spec *
|
||||
recursive-exclude superset/static/assets/src *
|
||||
recursive-include superset/static/assets/src/visualizations/CountryMap/ *
|
||||
recursive-exclude superset/static/images/viz_thumbnails_large *
|
||||
recursive-exclude superset/static/assets/node_modules *
|
||||
recursive-include superset/templates *
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
This file documents any backwards-incompatible changes in Superset and
|
||||
assists people when migrating to a new version.
|
||||
|
||||
## Superset 0.29.0
|
||||
* India was removed from the "Country Map" visualization as the geojson
|
||||
file included in the package was very large
|
||||
|
||||
## Superset 0.28.0
|
||||
* Support for Python 2 is deprecated, we only support >=3.6 from
|
||||
`0.28.0` onwards
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "superset",
|
||||
"version": "0.999.0dev",
|
||||
"version": "0.29.0rc7",
|
||||
"description": "Superset is a data exploration platform designed to be visual, intuitive, and interactive.",
|
||||
"license": "Apache-2.0",
|
||||
"directories": {
|
||||
@@ -51,11 +51,11 @@
|
||||
"@data-ui/sparkline": "^0.0.54",
|
||||
"@data-ui/theme": "^0.0.62",
|
||||
"@data-ui/xy-chart": "^0.0.61",
|
||||
"@superset-ui/chart": "^0.5.0",
|
||||
"@superset-ui/color": "^0.5.0",
|
||||
"@superset-ui/chart": "^0.7.0",
|
||||
"@superset-ui/color": "^0.7.0",
|
||||
"@superset-ui/connection": "^0.5.0",
|
||||
"@superset-ui/core": "^0.5.0",
|
||||
"@superset-ui/translation": "^0.5.0",
|
||||
"@superset-ui/core": "^0.7.0",
|
||||
"@superset-ui/translation": "^0.7.0",
|
||||
"@vx/legend": "^0.0.170",
|
||||
"@vx/responsive": "0.0.172",
|
||||
"@vx/scale": "^0.0.165",
|
||||
@@ -79,7 +79,7 @@
|
||||
"d3-tip": "^0.9.1",
|
||||
"datamaps": "^0.5.8",
|
||||
"datatables.net-bs": "^1.10.15",
|
||||
"deck.gl": "^5.3.4",
|
||||
"deck.gl": "^5.3.5",
|
||||
"distributions": "^1.0.0",
|
||||
"dnd-core": "^2.6.0",
|
||||
"dompurify": "^1.0.3",
|
||||
|
||||
@@ -19,10 +19,10 @@ describe('ColorPickerControl', () => {
|
||||
beforeEach(() => {
|
||||
getCategoricalSchemeRegistry()
|
||||
.registerValue('test', new CategoricalScheme({
|
||||
name: 'test',
|
||||
id: 'test',
|
||||
colors: ['red', 'green', 'blue'],
|
||||
}))
|
||||
.setDefaultSchemeName('test');
|
||||
.setDefaultKey('test');
|
||||
wrapper = shallow(<ColorPickerControl {...defaultProps} />);
|
||||
inst = wrapper.instance();
|
||||
});
|
||||
|
||||
@@ -29,6 +29,7 @@ describe('utils', () => {
|
||||
expect(d3format('.3s', 1234)).toBe('1.23k');
|
||||
expect(d3format('.3s', 1237)).toBe('1.24k');
|
||||
expect(d3format('', 1237)).toBe('1.24k');
|
||||
expect(d3format('.2efd2.ef.2.e', 1237)).toBe('1237 (Invalid format: .2efd2.ef.2.e)');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -45,8 +46,9 @@ describe('utils', () => {
|
||||
it('is a function', () => {
|
||||
expect(typeof d3TimeFormatPreset).toBe('function');
|
||||
});
|
||||
it('returns a working time formatter', () => {
|
||||
it('returns a working formatter', () => {
|
||||
expect(d3FormatPreset('smart_date')(0)).toBe('1970');
|
||||
expect(d3FormatPreset('%%GIBBERISH')(0)).toBe('0 (Invalid format: %%GIBBERISH)');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import {
|
||||
getBuckets,
|
||||
} from '../../../../src/visualizations/deckgl/utils';
|
||||
|
||||
const metricAccessor = d => d.count;
|
||||
|
||||
describe('getBreakPoints', () => {
|
||||
it('is a function', () => {
|
||||
expect(typeof getBreakPoints).toBe('function');
|
||||
@@ -11,7 +13,7 @@ describe('getBreakPoints', () => {
|
||||
|
||||
it('returns sorted break points', () => {
|
||||
const fd = { break_points: ['0', '10', '100', '50', '1000'] };
|
||||
const result = getBreakPoints(fd, []);
|
||||
const result = getBreakPoints(fd, [], metricAccessor);
|
||||
const expected = ['0', '10', '50', '100', '1000'];
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
@@ -19,7 +21,7 @@ describe('getBreakPoints', () => {
|
||||
it('returns evenly distributed break points when no break points are specified', () => {
|
||||
const fd = { metric: 'count' };
|
||||
const features = [0, 1, 2, 10].map(count => ({ count }));
|
||||
const result = getBreakPoints(fd, features);
|
||||
const result = getBreakPoints(fd, features, metricAccessor);
|
||||
const expected = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10'];
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
@@ -27,7 +29,7 @@ describe('getBreakPoints', () => {
|
||||
it('formats number with proper precision', () => {
|
||||
const fd = { metric: 'count', num_buckets: 2 };
|
||||
const features = [0, 1 / 3, 2 / 3, 1].map(count => ({ count }));
|
||||
const result = getBreakPoints(fd, features);
|
||||
const result = getBreakPoints(fd, features, metricAccessor);
|
||||
const expected = ['0.0', '0.5', '1.0'];
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
@@ -35,7 +37,7 @@ describe('getBreakPoints', () => {
|
||||
it('works with a zero range', () => {
|
||||
const fd = { metric: 'count', num_buckets: 1 };
|
||||
const features = [1, 1, 1].map(count => ({ count }));
|
||||
const result = getBreakPoints(fd, features);
|
||||
const result = getBreakPoints(fd, features, metricAccessor);
|
||||
const expected = ['1', '1'];
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
@@ -53,7 +55,7 @@ describe('getBreakPointColorScaler', () => {
|
||||
opacity: 100,
|
||||
};
|
||||
const features = [10, 20, 30].map(count => ({ count }));
|
||||
const scaler = getBreakPointColorScaler(fd, features);
|
||||
const scaler = getBreakPointColorScaler(fd, features, metricAccessor);
|
||||
expect(scaler({ count: 10 })).toEqual([0, 0, 0, 255]);
|
||||
expect(scaler({ count: 15 })).toEqual([64, 64, 64, 255]);
|
||||
expect(scaler({ count: 30 })).toEqual([255, 255, 255, 255]);
|
||||
@@ -67,7 +69,7 @@ describe('getBreakPointColorScaler', () => {
|
||||
opacity: 100,
|
||||
};
|
||||
const features = [];
|
||||
const scaler = getBreakPointColorScaler(fd, features);
|
||||
const scaler = getBreakPointColorScaler(fd, features, metricAccessor);
|
||||
expect(scaler({ count: 0 })).toEqual([0, 0, 0, 255]);
|
||||
expect(scaler({ count: 0.5 })).toEqual([0, 0, 0, 255]);
|
||||
expect(scaler({ count: 1 })).toEqual([255, 255, 255, 255]);
|
||||
@@ -82,7 +84,7 @@ describe('getBreakPointColorScaler', () => {
|
||||
opacity: 100,
|
||||
};
|
||||
const features = [];
|
||||
const scaler = getBreakPointColorScaler(fd, features);
|
||||
const scaler = getBreakPointColorScaler(fd, features, metricAccessor);
|
||||
expect(scaler({ count: -1 })).toEqual([0, 0, 0, 0]);
|
||||
expect(scaler({ count: 11 })).toEqual([255, 255, 255, 0]);
|
||||
});
|
||||
@@ -101,7 +103,7 @@ describe('getBuckets', () => {
|
||||
opacity: 100,
|
||||
};
|
||||
const features = [];
|
||||
const result = getBuckets(fd, features);
|
||||
const result = getBuckets(fd, features, metricAccessor);
|
||||
const expected = {
|
||||
'0 - 1': { color: [0, 0, 0, 255], enabled: true },
|
||||
'1 - 10': { color: [255, 255, 255, 255], enabled: true },
|
||||
|
||||
@@ -28,6 +28,7 @@ const defaultProps = {
|
||||
csv: true,
|
||||
actions: {},
|
||||
cache: false,
|
||||
database: {},
|
||||
};
|
||||
|
||||
const SEARCH_HEIGHT = 46;
|
||||
|
||||
@@ -149,7 +149,7 @@ class SqlEditor extends React.PureComponent {
|
||||
schema: qe.schema,
|
||||
tempTableName: ctas ? this.state.ctas : '',
|
||||
templateParams: qe.templateParams,
|
||||
queryLimit: qe.queryLimit,
|
||||
queryLimit: qe.queryLimit || this.props.defaultQueryLimit,
|
||||
runAsync,
|
||||
ctas,
|
||||
};
|
||||
|
||||
@@ -138,6 +138,7 @@ class TabbedSqlEditors extends React.PureComponent {
|
||||
sql: `${t(
|
||||
'-- Note: Unless you save your query, these tabs will NOT persist if you clear your cookies or change browsers.',
|
||||
)}\n\nSELECT ...`,
|
||||
queryLimit: this.props.defaultQueryLimit,
|
||||
};
|
||||
this.props.actions.addQueryEditor(qe);
|
||||
}
|
||||
|
||||
@@ -95,6 +95,14 @@ class Chart extends React.PureComponent {
|
||||
const { actions, chartId } = this.props;
|
||||
console.warn(error); // eslint-disable-line
|
||||
actions.chartRenderingFailed(error.toString(), chartId, info ? info.componentStack : null);
|
||||
|
||||
Logger.append(LOG_ACTIONS_RENDER_CHART, {
|
||||
slice_id: chartId,
|
||||
has_err: true,
|
||||
error_details: error.toString(),
|
||||
start_offset: this.renderStartTime,
|
||||
duration: Logger.getTimestamp() - this.renderStartTime,
|
||||
});
|
||||
}
|
||||
|
||||
prepareChartProps() {
|
||||
@@ -126,8 +134,7 @@ class Chart extends React.PureComponent {
|
||||
|
||||
renderTooltip() {
|
||||
const { tooltip } = this.state;
|
||||
|
||||
if (tooltip) {
|
||||
if (tooltip && tooltip.content) {
|
||||
return (
|
||||
<Tooltip
|
||||
className="chart-tooltip"
|
||||
@@ -173,32 +180,31 @@ class Chart extends React.PureComponent {
|
||||
this.renderStartTime = Logger.getTimestamp();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`chart-container ${isLoading ? 'is-loading' : ''}`}
|
||||
style={containerStyles}
|
||||
>
|
||||
{this.renderTooltip()}
|
||||
<ErrorBoundary onError={this.handleRenderFailure} showMessage={false}>
|
||||
<div
|
||||
className={`chart-container ${isLoading ? 'is-loading' : ''}`}
|
||||
style={containerStyles}
|
||||
>
|
||||
{this.renderTooltip()}
|
||||
|
||||
{['loading', 'success'].indexOf(chartStatus) >= 0 && <Loading size={50} />}
|
||||
{['loading', 'success'].indexOf(chartStatus) >= 0 && <Loading size={50} />}
|
||||
|
||||
{chartAlert && (
|
||||
<StackTraceMessage
|
||||
message={chartAlert}
|
||||
link={queryResponse ? queryResponse.link : null}
|
||||
stackTrace={chartStackTrace}
|
||||
/>
|
||||
)}
|
||||
{chartAlert && (
|
||||
<StackTraceMessage
|
||||
message={chartAlert}
|
||||
link={queryResponse ? queryResponse.link : null}
|
||||
stackTrace={chartStackTrace}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!isLoading && !chartAlert && isFaded && (
|
||||
<RefreshChartOverlay
|
||||
width={width}
|
||||
height={height}
|
||||
onQuery={onQuery}
|
||||
onDismiss={onDismissRefreshOverlay}
|
||||
/>
|
||||
)}
|
||||
|
||||
<ErrorBoundary onError={this.handleRenderFailure} showMessage={false}>
|
||||
{!isLoading && !chartAlert && isFaded && (
|
||||
<RefreshChartOverlay
|
||||
width={width}
|
||||
height={height}
|
||||
onQuery={onQuery}
|
||||
onDismiss={onDismissRefreshOverlay}
|
||||
/>
|
||||
)}
|
||||
<SuperChart
|
||||
className={`slice_container ${snakeCase(vizType)} ${isFaded ? ' faded' : ''}`}
|
||||
chartType={vizType}
|
||||
@@ -207,8 +213,8 @@ class Chart extends React.PureComponent {
|
||||
onRenderFailure={this.handleRenderFailure}
|
||||
skipRendering={skipChartRendering}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,15 +39,17 @@ function mapStateToProps(
|
||||
|
||||
// rows and columns need more data about their child dimensions
|
||||
// doing this allows us to not pass the entire component lookup to all Components
|
||||
const componentType = component.type;
|
||||
if (componentType === ROW_TYPE || componentType === COLUMN_TYPE) {
|
||||
const { occupiedWidth, minimumWidth } = getDetailedComponentWidth({
|
||||
id,
|
||||
components: dashboardLayout,
|
||||
});
|
||||
if (component) {
|
||||
const componentType = component.type;
|
||||
if (componentType === ROW_TYPE || componentType === COLUMN_TYPE) {
|
||||
const { occupiedWidth, minimumWidth } = getDetailedComponentWidth({
|
||||
id,
|
||||
components: dashboardLayout,
|
||||
});
|
||||
|
||||
if (componentType === ROW_TYPE) props.occupiedColumnCount = occupiedWidth;
|
||||
if (componentType === COLUMN_TYPE) props.minColumnWidth = minimumWidth;
|
||||
if (componentType === ROW_TYPE) props.occupiedColumnCount = occupiedWidth;
|
||||
if (componentType === COLUMN_TYPE) props.minColumnWidth = minimumWidth;
|
||||
}
|
||||
}
|
||||
|
||||
return props;
|
||||
@@ -68,7 +70,7 @@ function mapDispatchToProps(dispatch) {
|
||||
class DashboardComponent extends React.PureComponent {
|
||||
render() {
|
||||
const { component } = this.props;
|
||||
const Component = ComponentLookup[component.type];
|
||||
const Component = component ? ComponentLookup[component.type] : null;
|
||||
return Component ? <Component {...this.props} /> : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,7 +519,6 @@ export const controls = {
|
||||
'Egypt',
|
||||
'France',
|
||||
'Germany',
|
||||
'India',
|
||||
'Italy',
|
||||
'Portugal',
|
||||
'Morocco',
|
||||
|
||||
@@ -22,7 +22,13 @@ export function d3FormatPreset(format) {
|
||||
return formatDate;
|
||||
}
|
||||
if (format) {
|
||||
return d3Format(format);
|
||||
try {
|
||||
return d3Format(format);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(e);
|
||||
return value => `${value} (Invalid format: ${format})`;
|
||||
}
|
||||
}
|
||||
return defaultNumberFormatter;
|
||||
}
|
||||
@@ -45,12 +51,18 @@ export function d3format(format, number) {
|
||||
format = format || '.3s';
|
||||
// Formats a number and memoizes formatters to be reused
|
||||
if (!(format in formatters)) {
|
||||
formatters[format] = d3Format(format);
|
||||
try {
|
||||
formatters[format] = d3Format(format);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(e);
|
||||
return `${number} (Invalid format: ${format})`;
|
||||
}
|
||||
}
|
||||
try {
|
||||
return formatters[format](number);
|
||||
} catch (e) {
|
||||
return 'ERR';
|
||||
return `${number} (Invalid format: ${format})`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,15 +11,15 @@ export default function setupColors() {
|
||||
const categoricalSchemeRegistry = getCategoricalSchemeRegistry();
|
||||
[airbnb, categoricalD3, google, lyft].forEach((group) => {
|
||||
group.forEach((scheme) => {
|
||||
categoricalSchemeRegistry.registerValue(scheme.name, scheme);
|
||||
categoricalSchemeRegistry.registerValue(scheme.id, scheme);
|
||||
});
|
||||
});
|
||||
categoricalSchemeRegistry.setDefaultSchemeName('bnbColors');
|
||||
categoricalSchemeRegistry.setDefaultKey('bnbColors');
|
||||
|
||||
const sequentialSchemeRegistry = getSequentialSchemeRegistry();
|
||||
[sequentialCommon, sequentialD3].forEach((group) => {
|
||||
group.forEach((scheme) => {
|
||||
sequentialSchemeRegistry.registerValue(scheme.name, scheme);
|
||||
sequentialSchemeRegistry.registerValue(scheme.id, scheme);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ function Chord(element, props) {
|
||||
const div = d3.select(element);
|
||||
const { nodes, matrix } = data;
|
||||
const f = d3.format(numberFormat);
|
||||
const colorFn = CategoricalColorNamespace.getScale(colorScheme).toFunction();
|
||||
const colorFn = CategoricalColorNamespace.getScale(colorScheme);
|
||||
|
||||
const outerRadius = Math.min(width, height) / 2 - 10;
|
||||
const innerRadius = outerRadius - 24;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -47,7 +47,7 @@ class CustomHistogram extends React.PureComponent {
|
||||
yAxisLabel,
|
||||
} = this.props;
|
||||
|
||||
const colorFn = CategoricalColorNamespace.getScale(colorScheme).toFunction();
|
||||
const colorFn = CategoricalColorNamespace.getScale(colorScheme);
|
||||
const keys = data.map(d => d.key);
|
||||
const colorScale = scaleOrdinal({
|
||||
domain: keys,
|
||||
|
||||
@@ -95,7 +95,7 @@ function Icicle(element, props) {
|
||||
const hasTime = ['adv_anal', 'time_series'].indexOf(chartType) >= 0;
|
||||
const format = d3.format(numberFormat);
|
||||
const timeFormat = d3TimeFormatPreset(dateTimeFormat);
|
||||
const colorFn = CategoricalColorNamespace.getScale(colorScheme).toFunction();
|
||||
const colorFn = CategoricalColorNamespace.getScale(colorScheme);
|
||||
|
||||
div.selectAll('*').remove();
|
||||
const tooltip = div
|
||||
|
||||
@@ -60,7 +60,7 @@ function Rose(element, props) {
|
||||
const numGroups = datum[times[0]].length;
|
||||
const format = d3.format(numberFormat);
|
||||
const timeFormat = d3TimeFormatPreset(dateTimeFormat);
|
||||
const colorFn = CategoricalColorNamespace.getScale(colorScheme).toFunction();
|
||||
const colorFn = CategoricalColorNamespace.getScale(colorScheme);
|
||||
|
||||
d3.select('.nvtooltip').remove();
|
||||
div.selectAll('*').remove();
|
||||
|
||||
@@ -47,7 +47,7 @@ function Sankey(element, props) {
|
||||
.attr('class', 'sankey-tooltip')
|
||||
.style('opacity', 0);
|
||||
|
||||
const colorFn = CategoricalColorNamespace.getScale(colorScheme).toFunction();
|
||||
const colorFn = CategoricalColorNamespace.getScale(colorScheme);
|
||||
|
||||
const sankey = d3Sankey()
|
||||
.nodeWidth(15)
|
||||
|
||||
@@ -66,7 +66,7 @@ function Sunburst(element, props) {
|
||||
let arcs;
|
||||
let gMiddleText; // dom handles
|
||||
|
||||
const colorFn = CategoricalColorNamespace.getScale(colorScheme).toFunction();
|
||||
const colorFn = CategoricalColorNamespace.getScale(colorScheme);
|
||||
|
||||
// Helper + path gen functions
|
||||
const partition = d3.layout.partition()
|
||||
|
||||
@@ -68,7 +68,7 @@ function Treemap(element, props) {
|
||||
} = props;
|
||||
const div = d3.select(element);
|
||||
const formatNumber = d3.format(numberFormat);
|
||||
const colorFn = CategoricalColorNamespace.getScale(colorScheme).toFunction();
|
||||
const colorFn = CategoricalColorNamespace.getScale(colorScheme);
|
||||
const data = clone(rawData);
|
||||
|
||||
function draw(data, eltWidth, eltHeight) {
|
||||
|
||||
@@ -15,7 +15,7 @@ const { getScale } = CategoricalColorNamespace;
|
||||
function getCategories(fd, data) {
|
||||
const c = fd.color_picker || { r: 0, g: 0, b: 0, a: 1 };
|
||||
const fixedColor = [c.r, c.g, c.b, 255 * c.a];
|
||||
const colorFn = getScale(fd.color_scheme).toFunction();
|
||||
const colorFn = getScale(fd.color_scheme);
|
||||
const categories = {};
|
||||
data.forEach((d) => {
|
||||
if (d.cat_color != null && !categories.hasOwnProperty(d.cat_color)) {
|
||||
@@ -159,7 +159,7 @@ export default class CategoricalDeckGLContainer extends React.PureComponent {
|
||||
}
|
||||
addColor(data, fd) {
|
||||
const c = fd.color_picker || { r: 0, g: 0, b: 0, a: 1 };
|
||||
const colorFn = getScale(fd.color_scheme).toFunction();
|
||||
const colorFn = getScale(fd.color_scheme);
|
||||
return data.map((d) => {
|
||||
let color;
|
||||
if (fd.dimension) {
|
||||
|
||||
@@ -3,8 +3,9 @@ import PropTypes from 'prop-types';
|
||||
import MapGL from 'react-map-gl';
|
||||
import DeckGL from 'deck.gl';
|
||||
import 'mapbox-gl/dist/mapbox-gl.css';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
const TICK = 1000; // milliseconds
|
||||
const TICK = 2000; // milliseconds
|
||||
|
||||
const propTypes = {
|
||||
viewport: PropTypes.object.isRequired,
|
||||
@@ -23,12 +24,13 @@ const defaultProps = {
|
||||
export default class DeckGLContainer extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.tick = this.tick.bind(this);
|
||||
this.onViewportChange = this.onViewportChange.bind(this);
|
||||
// This has to be placed after this.tick is bound to this
|
||||
this.state = {
|
||||
previousViewport: props.viewport,
|
||||
timer: setInterval(this.tick, TICK),
|
||||
};
|
||||
this.tick = this.tick.bind(this);
|
||||
this.onViewportChange = this.onViewportChange.bind(this);
|
||||
}
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
if (nextProps.viewport !== prevState.viewport) {
|
||||
@@ -53,7 +55,9 @@ export default class DeckGLContainer extends React.Component {
|
||||
}
|
||||
tick() {
|
||||
// Limiting updating viewport controls through Redux at most 1*sec
|
||||
if (this.state && this.state.previousViewport !== this.props.viewport) {
|
||||
// Deep compare is needed as shallow equality doesn't work here, viewport object
|
||||
// changes id at every change
|
||||
if (this.state && !isEqual(this.state.previousViewport, this.props.viewport)) {
|
||||
const setCV = this.props.setControlValue;
|
||||
const vp = this.props.viewport;
|
||||
if (setCV) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import DeckGLContainer from './DeckGLContainer';
|
||||
import CategoricalDeckGLContainer from './CategoricalDeckGLContainer';
|
||||
import { fitViewport } from './layers/common';
|
||||
@@ -18,36 +20,66 @@ const defaultProps = {
|
||||
};
|
||||
|
||||
export function createDeckGLComponent(getLayer, getPoints) {
|
||||
function Component(props) {
|
||||
const {
|
||||
formData,
|
||||
payload,
|
||||
setControlValue,
|
||||
onAddFilter,
|
||||
setTooltip,
|
||||
viewport: originalViewport,
|
||||
} = props;
|
||||
|
||||
const viewport = formData.autozoom
|
||||
? fitViewport(originalViewport, getPoints(payload.data.features))
|
||||
: originalViewport;
|
||||
|
||||
const layer = getLayer(formData, payload, onAddFilter, setTooltip);
|
||||
|
||||
return (
|
||||
<DeckGLContainer
|
||||
mapboxApiAccessToken={payload.data.mapboxApiKey}
|
||||
viewport={viewport}
|
||||
layers={[layer]}
|
||||
mapStyle={formData.mapbox_style}
|
||||
setControlValue={setControlValue}
|
||||
/>
|
||||
);
|
||||
// Higher order component
|
||||
class Component extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const originalViewport = props.viewport;
|
||||
const viewport = props.formData.autozoom
|
||||
? fitViewport(originalViewport, getPoints(props.payload.data.features))
|
||||
: originalViewport;
|
||||
this.state = {
|
||||
viewport,
|
||||
layer: this.computeLayer(props),
|
||||
};
|
||||
this.onViewportChange = this.onViewportChange.bind(this);
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
// Only recompute the layer if anything BUT the viewport has changed
|
||||
const nextFdNoVP = { ...nextProps.formData, viewport: null };
|
||||
const currFdNoVP = { ...this.props.formData, viewport: null };
|
||||
if (
|
||||
!isEqual(nextFdNoVP, currFdNoVP) ||
|
||||
nextProps.payload !== this.props.payload
|
||||
) {
|
||||
this.setState({ layer: this.computeLayer(nextProps) });
|
||||
}
|
||||
}
|
||||
onViewportChange(viewport) {
|
||||
this.setState({ viewport });
|
||||
}
|
||||
computeLayer(props) {
|
||||
const {
|
||||
formData,
|
||||
payload,
|
||||
onAddFilter,
|
||||
setTooltip,
|
||||
} = props;
|
||||
return getLayer(formData, payload, onAddFilter, setTooltip);
|
||||
}
|
||||
render() {
|
||||
const {
|
||||
formData,
|
||||
payload,
|
||||
setControlValue,
|
||||
} = this.props;
|
||||
const {
|
||||
layer,
|
||||
viewport,
|
||||
} = this.state;
|
||||
return (
|
||||
<DeckGLContainer
|
||||
mapboxApiAccessToken={payload.data.mapboxApiKey}
|
||||
viewport={viewport}
|
||||
layers={[layer]}
|
||||
mapStyle={formData.mapbox_style}
|
||||
setControlValue={setControlValue}
|
||||
onViewportChange={this.onViewportChange}
|
||||
/>);
|
||||
}
|
||||
}
|
||||
|
||||
Component.propTypes = propTypes;
|
||||
Component.defaultProps = defaultProps;
|
||||
|
||||
return Component;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,10 +48,12 @@ export function getLayer(formData, payload, setTooltip, selected, onSelect, filt
|
||||
data = jsFnMutator(data);
|
||||
}
|
||||
|
||||
const metricLabel = fd.metric ? fd.metric.label || fd.metric : null;
|
||||
const accessor = d => d[metricLabel];
|
||||
// base color for the polygons
|
||||
const baseColorScaler = fd.metric === null
|
||||
? () => [fc.r, fc.g, fc.b, 255 * fc.a]
|
||||
: getBreakPointColorScaler(fd, data);
|
||||
: getBreakPointColorScaler(fd, data, accessor);
|
||||
|
||||
// when polygons are selected, reduce the opacity of non-selected polygons
|
||||
const colorScaler = (d) => {
|
||||
@@ -61,7 +63,6 @@ export function getLayer(formData, payload, setTooltip, selected, onSelect, filt
|
||||
}
|
||||
return baseColor;
|
||||
};
|
||||
|
||||
return new PolygonLayer({
|
||||
id: `path-layer-${fd.slice_id}`,
|
||||
data,
|
||||
@@ -211,7 +212,12 @@ class DeckGLPolygon extends React.Component {
|
||||
render() {
|
||||
const { payload, formData, setControlValue } = this.props;
|
||||
const { start, end, getStep, values, disabled, viewport } = this.state;
|
||||
const buckets = getBuckets(formData, payload.data.features);
|
||||
|
||||
const fd = formData;
|
||||
const metricLabel = fd.metric ? fd.metric.label || fd.metric : null;
|
||||
const accessor = d => d[metricLabel];
|
||||
|
||||
const buckets = getBuckets(formData, payload.data.features, accessor);
|
||||
return (
|
||||
<div style={{ position: 'relative' }}>
|
||||
<AnimatableDeckGLContainer
|
||||
|
||||
@@ -38,12 +38,13 @@ export function commonLayerProps(formData, setTooltip, onSelect) {
|
||||
let tooltipContentGenerator;
|
||||
if (fd.js_tooltip) {
|
||||
tooltipContentGenerator = sandboxedEval(fd.js_tooltip);
|
||||
} else if (fd.line_column && fd.line_type === 'geohash') {
|
||||
} else if (fd.line_column && fd.metric && ['geohash', 'zipcode'].indexOf(fd.line_type) >= 0) {
|
||||
const metricLabel = fd.metric.label || fd.metric;
|
||||
tooltipContentGenerator = o => (
|
||||
<div>
|
||||
<div>{fd.line_column}: <strong>{o.object[fd.line_column]}</strong></div>
|
||||
{fd.metric &&
|
||||
<div>{fd.metric}: <strong>{o.object[fd.metric]}</strong></div>}
|
||||
<div>{metricLabel}: <strong>{o.object[metricLabel]}</strong></div>}
|
||||
</div>);
|
||||
}
|
||||
if (tooltipContentGenerator) {
|
||||
|
||||
@@ -3,20 +3,19 @@ import { scaleThreshold } from 'd3-scale';
|
||||
import { getSequentialSchemeRegistry, SequentialScheme } from '@superset-ui/color';
|
||||
import { hexToRGB } from '../../modules/colors';
|
||||
|
||||
const DEFAULT_NUM_BUCKETS = 10;
|
||||
|
||||
export function getBreakPoints({
|
||||
break_points: formDataBreakPoints,
|
||||
num_buckets: formDataNumBuckets,
|
||||
metric,
|
||||
}, features) {
|
||||
}, features, accessor) {
|
||||
if (!features) {
|
||||
return [];
|
||||
}
|
||||
if (formDataBreakPoints === undefined || formDataBreakPoints.length === 0) {
|
||||
// compute evenly distributed break points based on number of buckets
|
||||
const numBuckets = formDataNumBuckets
|
||||
? parseInt(formDataNumBuckets, 10)
|
||||
: 10;
|
||||
const [minValue, maxValue] = extent(features, d => d[metric]);
|
||||
const numBuckets = formDataNumBuckets ? parseInt(formDataNumBuckets, 10) : DEFAULT_NUM_BUCKETS;
|
||||
const [minValue, maxValue] = extent(features, accessor);
|
||||
const delta = (maxValue - minValue) / numBuckets;
|
||||
const precision = delta === 0
|
||||
? 0
|
||||
@@ -32,19 +31,17 @@ export function getBreakPointColorScaler({
|
||||
break_points: formDataBreakPoints,
|
||||
num_buckets: formDataNumBuckets,
|
||||
linear_color_scheme: linearColorScheme,
|
||||
metric,
|
||||
opacity,
|
||||
}, features) {
|
||||
}, features, accessor) {
|
||||
const breakPoints = formDataBreakPoints || formDataNumBuckets
|
||||
? getBreakPoints({
|
||||
break_points: formDataBreakPoints,
|
||||
num_buckets: formDataNumBuckets,
|
||||
metric,
|
||||
}, features)
|
||||
}, features, accessor)
|
||||
: null;
|
||||
const colorScheme = Array.isArray(linearColorScheme)
|
||||
? new SequentialScheme({
|
||||
name: 'custom',
|
||||
id: 'custom',
|
||||
colors: linearColorScheme,
|
||||
})
|
||||
: getSequentialSchemeRegistry().get(linearColorScheme);
|
||||
@@ -69,13 +66,14 @@ export function getBreakPointColorScaler({
|
||||
maskPoint = value => value > breakPoints[n] || value < breakPoints[0];
|
||||
} else {
|
||||
// interpolate colors linearly
|
||||
scaler = colorScheme.createLinearScale(extent(features, d => d[metric]));
|
||||
scaler = colorScheme.createLinearScale(extent(features, accessor));
|
||||
maskPoint = () => false;
|
||||
}
|
||||
|
||||
return (d) => {
|
||||
const c = hexToRGB(scaler(d[metric]));
|
||||
if (maskPoint(d[metric])) {
|
||||
const v = accessor(d);
|
||||
const c = hexToRGB(scaler(v));
|
||||
if (maskPoint(v)) {
|
||||
c[3] = 0;
|
||||
} else {
|
||||
c[3] = (opacity / 100.0) * 255;
|
||||
@@ -84,15 +82,15 @@ export function getBreakPointColorScaler({
|
||||
};
|
||||
}
|
||||
|
||||
export function getBuckets(fd, features) {
|
||||
const breakPoints = getBreakPoints(fd, features, true);
|
||||
const colorScaler = getBreakPointColorScaler(fd, features);
|
||||
export function getBuckets(fd, features, accessor) {
|
||||
const breakPoints = getBreakPoints(fd, features, accessor);
|
||||
const colorScaler = getBreakPointColorScaler(fd, features, accessor);
|
||||
const buckets = {};
|
||||
breakPoints.slice(1).forEach((value, i) => {
|
||||
const range = breakPoints[i] + ' - ' + breakPoints[i + 1];
|
||||
const mid = 0.5 * (parseInt(breakPoints[i], 10) + parseInt(breakPoints[i + 1], 10));
|
||||
buckets[range] = {
|
||||
color: colorScaler({ [fd.metric]: mid }),
|
||||
color: colorScaler({ [fd.metric.label || fd.metric]: mid }),
|
||||
enabled: true,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -456,11 +456,13 @@ function nvd3Vis(element, props) {
|
||||
chart.xScale(d3.scale.log());
|
||||
}
|
||||
|
||||
let xAxisFormatter = d3FormatPreset(xAxisFormat);
|
||||
let xAxisFormatter;
|
||||
if (isTimeSeries) {
|
||||
xAxisFormatter = d3TimeFormatPreset(xAxisFormat);
|
||||
// In tooltips, always use the verbose time format
|
||||
chart.interactiveLayer.tooltip.headerFormatter(formatDateVerbose);
|
||||
} else {
|
||||
xAxisFormatter = d3FormatPreset(xAxisFormat);
|
||||
}
|
||||
if (chart.x2Axis && chart.x2Axis.tickFormat) {
|
||||
chart.x2Axis.tickFormat(xAxisFormatter);
|
||||
@@ -504,7 +506,7 @@ function nvd3Vis(element, props) {
|
||||
});
|
||||
}
|
||||
} else if (vizType !== 'bullet') {
|
||||
const colorFn = getScale(colorScheme).toFunction();
|
||||
const colorFn = getScale(colorScheme);
|
||||
chart.color(d => d.color || colorFn(cleanColorInput(d[colorKey])));
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ const TIME_SHIFT_PATTERN = /\d+ \w+ offset/;
|
||||
export function cleanColorInput(value) {
|
||||
// for superset series that should have the same color
|
||||
return String(value).trim()
|
||||
.toLowerCase()
|
||||
.split(', ')
|
||||
.filter(k => !TIME_SHIFT_PATTERN.test(k))
|
||||
.join(', ');
|
||||
|
||||
@@ -48,7 +48,7 @@ function WordCloud(element, props) {
|
||||
.fontWeight('bold')
|
||||
.fontSize(d => scale(d.size));
|
||||
|
||||
const colorFn = CategoricalColorNamespace.getScale(colorScheme).toFunction();
|
||||
const colorFn = CategoricalColorNamespace.getScale(colorScheme);
|
||||
|
||||
function draw(words) {
|
||||
chart.selectAll('*').remove();
|
||||
|
||||
@@ -307,30 +307,30 @@
|
||||
d3-array "^1.2.0"
|
||||
prop-types "^15.5.10"
|
||||
|
||||
"@deck.gl/core@^5.3.3":
|
||||
version "5.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@deck.gl/core/-/core-5.3.3.tgz#a13c07e5fa3e22297fd450d6da8ab9aac334b1f0"
|
||||
"@deck.gl/core@^5.3.5":
|
||||
version "5.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@deck.gl/core/-/core-5.3.5.tgz#24e6b3164a36b89b05cd020c2f7bcb5d08e3c266"
|
||||
dependencies:
|
||||
luma.gl "^5.3.0"
|
||||
luma.gl "^5.3.1"
|
||||
math.gl "^1.2.1"
|
||||
mjolnir.js "^1.0.0"
|
||||
probe.gl "^1.0.0"
|
||||
seer "^0.2.4"
|
||||
viewport-mercator-project "^5.1.0"
|
||||
|
||||
"@deck.gl/layers@^5.3.4":
|
||||
version "5.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@deck.gl/layers/-/layers-5.3.4.tgz#ab3de1bf8bb68d67772642acbb4e0f87f4f11300"
|
||||
"@deck.gl/layers@^5.3.5":
|
||||
version "5.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@deck.gl/layers/-/layers-5.3.5.tgz#79c19be42961b909772e9dcd4eef5416d226d58e"
|
||||
dependencies:
|
||||
"@deck.gl/core" "^5.3.3"
|
||||
"@deck.gl/core" "^5.3.5"
|
||||
d3-hexbin "^0.2.1"
|
||||
earcut "^2.0.6"
|
||||
|
||||
"@deck.gl/react@^5.3.3":
|
||||
version "5.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@deck.gl/react/-/react-5.3.3.tgz#e7352934f6742d3ce672a394cbff312aab5ccaa0"
|
||||
"@deck.gl/react@^5.3.5":
|
||||
version "5.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@deck.gl/react/-/react-5.3.5.tgz#caace72d4afc6531103fbb87292400de7c6f292a"
|
||||
dependencies:
|
||||
"@deck.gl/core" "^5.3.3"
|
||||
"@deck.gl/core" "^5.3.5"
|
||||
prop-types "^15.6.0"
|
||||
|
||||
"@mapbox/geojson-area@0.2.2":
|
||||
@@ -399,18 +399,18 @@
|
||||
dependencies:
|
||||
array-from "^2.1.1"
|
||||
|
||||
"@superset-ui/chart@^0.5.0":
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@superset-ui/chart/-/chart-0.5.0.tgz#1420ce7b6ac3bf1b06875e5f2541fa98505b5a9b"
|
||||
"@superset-ui/chart@^0.7.0":
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@superset-ui/chart/-/chart-0.7.0.tgz#c297642bf6968e9e24b8de68c595942ad02da949"
|
||||
dependencies:
|
||||
"@superset-ui/core" "^0.3.0"
|
||||
reselect "^4.0.0"
|
||||
|
||||
"@superset-ui/color@^0.5.0":
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@superset-ui/color/-/color-0.5.0.tgz#3064bec063fdf43d568a39b4d9ded352d0aeed55"
|
||||
"@superset-ui/color@^0.7.0":
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@superset-ui/color/-/color-0.7.0.tgz#9d3eca8da493e1241a047f88b06d668b2cd20e3c"
|
||||
dependencies:
|
||||
"@superset-ui/core" "^0.5.0"
|
||||
"@superset-ui/core" "^0.7.0"
|
||||
d3-scale "^2.1.2"
|
||||
|
||||
"@superset-ui/connection@^0.5.0":
|
||||
@@ -426,15 +426,15 @@
|
||||
dependencies:
|
||||
lodash "^4.17.11"
|
||||
|
||||
"@superset-ui/core@^0.5.0":
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@superset-ui/core/-/core-0.5.0.tgz#8784465e312cac5015df28d8540d27a2fd6060ba"
|
||||
"@superset-ui/core@^0.7.0":
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@superset-ui/core/-/core-0.7.0.tgz#aa116248d56fd22d57a7bbc9afbdfc0cb367146a"
|
||||
dependencies:
|
||||
lodash "^4.17.11"
|
||||
|
||||
"@superset-ui/translation@^0.5.0":
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@superset-ui/translation/-/translation-0.5.0.tgz#d02af7be94ac6b9e48d3b09f9f1059b11b2d283d"
|
||||
"@superset-ui/translation@^0.7.0":
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@superset-ui/translation/-/translation-0.7.0.tgz#8b9426a97d523df5aefe9242084264897efe252c"
|
||||
dependencies:
|
||||
jed "^1.1.1"
|
||||
|
||||
@@ -3679,13 +3679,13 @@ decimal.js@9.0.1:
|
||||
version "9.0.1"
|
||||
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-9.0.1.tgz#1cc8b228177da7ab6498c1cc06eb130a290e6e1e"
|
||||
|
||||
deck.gl@^5.3.4:
|
||||
version "5.3.4"
|
||||
resolved "https://registry.yarnpkg.com/deck.gl/-/deck.gl-5.3.4.tgz#35e5a7087ef0d8ca7811d06a721ea289edbe7c24"
|
||||
deck.gl@^5.3.5:
|
||||
version "5.3.5"
|
||||
resolved "https://registry.yarnpkg.com/deck.gl/-/deck.gl-5.3.5.tgz#a30b8a6ee6caba1133167de461edc15ee3bb0f8b"
|
||||
dependencies:
|
||||
"@deck.gl/core" "^5.3.3"
|
||||
"@deck.gl/layers" "^5.3.4"
|
||||
"@deck.gl/react" "^5.3.3"
|
||||
"@deck.gl/core" "^5.3.5"
|
||||
"@deck.gl/layers" "^5.3.5"
|
||||
"@deck.gl/react" "^5.3.5"
|
||||
|
||||
decode-uri-component@^0.2.0:
|
||||
version "0.2.0"
|
||||
@@ -7241,9 +7241,9 @@ lru-cache@^4.0.1, lru-cache@^4.1.1, lru-cache@^4.1.3:
|
||||
pseudomap "^1.0.2"
|
||||
yallist "^2.1.2"
|
||||
|
||||
luma.gl@^5.3.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/luma.gl/-/luma.gl-5.3.0.tgz#a93b2f34489d8230eb6d8c871335800d9b83ee67"
|
||||
luma.gl@^5.3.1:
|
||||
version "5.3.1"
|
||||
resolved "https://registry.yarnpkg.com/luma.gl/-/luma.gl-5.3.1.tgz#d380d554fe4c9de0b883e5d75f58d2a2142cfa05"
|
||||
dependencies:
|
||||
math.gl "^1.1.0"
|
||||
probe.gl "^1.0.0"
|
||||
|
||||
@@ -489,7 +489,7 @@ class BaseViz(object):
|
||||
return df.to_csv(index=include_index, **config.get('CSV_EXPORT'))
|
||||
|
||||
def get_data(self, df):
|
||||
return self.get_df().to_dict(orient='records')
|
||||
return df.to_dict(orient='records')
|
||||
|
||||
@property
|
||||
def json_data(self):
|
||||
|
||||
Reference in New Issue
Block a user