Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix multilayer geoviz and color picker error #5767

Merged
merged 4 commits into from
Aug 29, 2018
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ function getCategories(fd, data) {

const propTypes = {
slice: PropTypes.object.isRequired,
data: PropTypes.array.isRequired,
mapboxApiKey: PropTypes.string.isRequired,
setControlValue: PropTypes.func.isRequired,
viewport: PropTypes.object.isRequired,
getLayer: PropTypes.func.isRequired,
payload: PropTypes.object.isRequired,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove data from the props, since it can fetched from the payload.

};

export default class CategoricalDeckGLContainer extends React.PureComponent {
Expand All @@ -50,9 +50,9 @@ export default class CategoricalDeckGLContainer extends React.PureComponent {
const fd = nextProps.slice.formData;

const timeGrain = fd.time_grain_sqla || fd.granularity || 'PT1M';
const timestamps = nextProps.data.map(f => f.__timestamp);
const timestamps = nextProps.payload.data.features.map(f => f.__timestamp);
const { start, end, step, values, disabled } = getPlaySliderParams(timestamps, timeGrain);
const categories = getCategories(fd, nextProps.data);
const categories = getCategories(fd, nextProps.payload.data.features);

return { start, end, step, values, disabled, categories };
}
Expand Down Expand Up @@ -80,7 +80,7 @@ export default class CategoricalDeckGLContainer extends React.PureComponent {
}
getLayers(values) {
const fd = this.props.slice.formData;
let data = [...this.props.data];
let data = [...this.props.payload.data.features];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you're making a copy of this.props.payload.data.features, mutating it, and then assigning it back to payload in line 107. Here you can simply do:

let data = this.props.payload.data.features;

And then as you mutate it, payload.data.features is also mutated. This way you can simply pass the original payload to getLayer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did some test and found it does not work. I guess it is because the location that data references got changed so payload.data.features will not get updated.

This is part of the code:

    let data = [...this.props.payload.data.features];

    // Add colors from categories or fixed color
    data = this.addColor(data, fd);

If we change it to let data = this.props.payload.data.features; and then data get reassigned by data = this.addColor(data, fd);, it is not referencing to the location of this.props.payload.data.features anymore so this.props.payload.data.features will not be changed.


// Add colors from categories or fixed color
data = this.addColor(data, fd);
Expand All @@ -103,7 +103,9 @@ export default class CategoricalDeckGLContainer extends React.PureComponent {
data = data.filter(d => this.state.categories[d.cat_color].enabled);
}

return [this.props.getLayer(fd, data, this.props.slice)];
const payload = this.props.payload;
payload.data.features = data;
return [this.props.getLayer(fd, payload, this.props.slice)];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can simply be written as:

return [this.props.getLayer(fd, this.props.payload, this.props.slice)];

If you follow my comment above. You can also do in the beginning of the function:

const { getLayer, payload, slice } = this.props;

The you can simple refer to getLayer, payload and slice inside the function, instead of this.props.getLayer, this.props.payload and this.props.slice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of good educations here! Thanks!
The reason why I am making a copy of this.props.payload.data.features, mutating it, and then assigning it back to payload in line 107 is that if I understand correctly, changing in this.props will result in re-render. As we are only passing it to getLayer function and don't need it to be re-rendered, I copy it so props is unaffected and unnecessary re-render can be avoided. This is my understanding but I am not very sure whether it is correct based on your suggestion. Please correct me if I am wrong.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well. I am wrong. Only state change will cause re-render.

}
toggleCategory(category) {
const categoryState = this.state.categories[category];
Expand Down
7 changes: 4 additions & 3 deletions superset/assets/src/visualizations/deckgl/layers/arc.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ function getPoints(data) {
return points;
}

function getLayer(fd, data, slice) {
function getLayer(fd, payload, slice) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I simplified the getLayer interface but forgot that other deck.gl vizs need other attributes in the payload.

const data = payload.data.features;
const sc = fd.color_picker;
const tc = fd.target_color_picker;
return new ArcLayer({
Expand All @@ -40,17 +41,17 @@ function deckArc(slice, payload, setControlValue) {
};

if (fd.autozoom) {
viewport = common.fitViewport(viewport, getPoints(payload.data.arcs));
viewport = common.fitViewport(viewport, getPoints(payload.data.features));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: you can use data here instead of payload.data.features.

Copy link
Contributor Author

@youngyjd youngyjd Aug 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems not. data is not defined in function deckArc(slice, payload, setControlValue){} so I feel using data here will not work. It is extracted in function getLayer(fd, payload, slice){} instead.

function getLayer(fd, payload, slice) {
  const data = payload.data.features;
  const sc = fd.color_picker;
  const tc = fd.target_color_picker;
  return new ArcLayer({
    id: `path-layer-${fd.slice_id}`,
    data,
    getSourceColor: d => d.sourceColor || d.color || [sc.r, sc.g, sc.b, 255 * sc.a],
    getTargetColor: d => d.targetColor || d.color || [tc.r, tc.g, tc.b, 255 * tc.a],
    strokeWidth: (fd.stroke_width) ? fd.stroke_width : 3,
    ...common.commonLayerProps(fd, slice),
  });
}

function deckArc(slice, payload, setControlValue) {
  const fd = slice.formData;
  let viewport = {
    ...fd.viewport,
    width: slice.width(),
    height: slice.height(),
  };

  if (fd.autozoom) {
    viewport = common.fitViewport(viewport, getPoints(payload.data.features));
  }
...
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, you're right. I got confused by the hidden lines, my bad!

}

ReactDOM.render(
<CategoricalDeckGLContainer
slice={slice}
data={payload.data.arcs}
mapboxApiKey={payload.data.mapboxApiKey}
setControlValue={setControlValue}
viewport={viewport}
getLayer={getLayer}
payload={payload}
/>,
document.getElementById(slice.containerId),
);
Expand Down
13 changes: 9 additions & 4 deletions superset/assets/src/visualizations/deckgl/layers/scatter.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,18 @@ function getPoints(data) {
return data.map(d => d.position);
}

function getLayer(fd, data, slice) {
const dataWithRadius = data.map((d) => {
function getLayer(fd, payload, slice) {
const dataWithRadius = payload.data.features.map((d) => {
let radius = unitToRadius(fd.point_unit, d.radius) || 10;
if (fd.multiplier) {
radius *= fd.multiplier;
}
return { ...d, radius };
if (d.color) {
return { ...d, radius };
}
const c = fd.color_picker || { r: 0, g: 0, b: 0, a: 1 };
const color = [c.r, c.g, c.b, c.a * 255];
return { ...d, radius, color };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for fixing this, I think I must've removed it by mistake.

});

return new ScatterplotLayer({
Expand Down Expand Up @@ -49,11 +54,11 @@ function deckScatter(slice, payload, setControlValue) {
ReactDOM.render(
<CategoricalDeckGLContainer
slice={slice}
data={payload.data.features}
mapboxApiKey={payload.data.mapboxApiKey}
setControlValue={setControlValue}
viewport={viewport}
getLayer={getLayer}
payload={payload}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove data above, since we're passing the payload.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure! make sense

/>,
document.getElementById(slice.containerId),
);
Expand Down
9 changes: 5 additions & 4 deletions superset/assets/src/visualizations/deckgl/multi.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@ function deckMulti(slice, payload, setControlValue) {
// Filters applied to multi_deck are passed down to underlying charts
// note that dashboard contextual information (filter_immune_slices and such) aren't
// taken into consideration here
let filters = subslice.form_data.filters.concat(fd.filters);
if (fd.extra_filters) {
filters = filters.concat(fd.extra_filters);
}
const filters = [
...(subslice.form_data.filters || []),
...(fd.filters || []),
...(fd.extra_filters || []),
];
const subsliceCopy = {
...subslice,
form_data: {
Expand Down
5 changes: 2 additions & 3 deletions superset/viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -2161,7 +2161,7 @@ def query_obj(self):
fd = self.form_data

# add NULL filters
if fd.get('filter_nulls'):
if fd.get('filter_nulls', True):
self.add_null_filters()

d = super(BaseDeckGLViz, self).query_obj()
Expand Down Expand Up @@ -2409,10 +2409,9 @@ def get_properties(self, d):

def get_data(self, df):
d = super(DeckArc, self).get_data(df)
arcs = d['features']

return {
'arcs': arcs,
'features': d['features'],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 I think it's good to standardize on the key value here.

'mapboxApiKey': config.get('MAPBOX_API_KEY'),
}

Expand Down