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

Adding negative function to maps #1357

Merged
merged 4 commits into from
Oct 8, 2024
Merged
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
104 changes: 95 additions & 9 deletions src/client/app/components/MapChartComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { AreaUnitType, getAreaUnitConversion } from '../utils/getAreaUnitConvers
import getGraphColor from '../utils/getGraphColor';
import translate from '../utils/translate';
import SpinnerComponent from './SpinnerComponent';
import { showInfoNotification } from '../utils/notifications';

/**
* @returns map component
Expand Down Expand Up @@ -82,7 +83,7 @@ export default function MapChartComponent() {
// Holds the hover text for each point for Plotly
const hoverText: string[] = [];
// Holds the size of each circle for Plotly.
const size: number[] = [];
let size: number[] = [];
// Holds the color of each circle for Plotly.
const colors: string[] = [];
// If there is no map then use a new, empty image as the map. I believe this avoids errors
Expand Down Expand Up @@ -289,22 +290,105 @@ export default function MapChartComponent() {
}
}
}

// TODO DEBUG Using amp 1, 2 & 3 from test data that within map. This arbitrarily changes the value for testing.
huss marked this conversation as resolved.
Show resolved Hide resolved
// It does not change the hover value.
// size[0] = 300;
// size[1] = 100;
// size[2] = -1;
// size = size.slice(0, x.length);

// TODO Using the following seems to have no impact on the code. It has been noticed that this function is called
// many times for each change. Someone should look at why that is happening and why some have no items in the arrays.
// if (size.length > 0) {
// TODO The max circle diameter should come from admin/DB.
const maxFeatureFraction = map.circleSize;
// Find the smaller of width and height. This is used since it means the circle size will be
// scaled to that dimension and smaller relative to the other coordinate.
const minDimension = Math.min(imageDimensionNormalized.width, imageDimensionNormalized.height);
// The circle size is set to area below. Thus, we need to convert from wanting a max
// diameter of minDimension * maxFeatureFraction to an area.
const maxCircleSize = Math.PI * Math.pow(minDimension * maxFeatureFraction / 2, 2);
// Find the largest circle which is usage.
const largestCircleSize = Math.max(...size);
// Scale largest circle to the max size and others will be scaled to be smaller.
// Not that < 1 => a larger circle.
const scaling = largestCircleSize / maxCircleSize;
// What fraction of the max circle size that the min circle size will be. Determine empirically.
let minFeatureFractionOfMax = 0.05;
// If the maxFeatureFraction is too small then it is possible that the min circle will be very
// small and difficult to see. This value is the min circle size to make sure that does not happen.
// The value used was empirically determined so it would not be too small.
const circleSizeThreshold = 75;
// If the circle will be too small then force to min, otherwise use standard value.
minFeatureFractionOfMax = minFeatureFractionOfMax * maxCircleSize < circleSizeThreshold ?
circleSizeThreshold / maxCircleSize : minFeatureFractionOfMax;
// Find the min and max of the values to graph.
const min = Math.min(...size);
const max = Math.max(...size);
// Fix the range/difference between the max and min value.
const range = max - min;
// This is the min value that should be graphed if all the values were positive. This treats the
// values as if they started at zero. Thus, the minValue is the fraction of the max on this
// shifted range that will have a circle size at least the fraction of the max that is allowed.
const minValue = minFeatureFractionOfMax * range;
// TODO As also noted above, this component is rerendering multiple times. This is causing the
// information message to appear multiple times. This needs to be figured out.
// Debugging indicates that this happens when selecting a meter/group not recently used so the
// data is not in Redux store. Each time it has readingsData as undefined so it does not process
// that meter. Need to see how can avoid this.

// Stores the amount to shift values for circle size for graphing where it is normally negative.
// Since subtract it adds to the value. It is negative for when the min is negative.
let shift;
if (range === 0) {
// All the values are the same including only one value and only zero.
// Plotly does not show circles of size 0 even if sizemin is set (hover does happen).
// To fix this, make the shift be min - 0.000123 (arbitrary value) so it will have a circle
// of the max size. The shifted value will be slightly positive (0.000123) so the circle shows.
// Note other cases avoid zero values if not the only one.
shift = min - 0.000123;
} else if (size.length != 0 && max < 0) {
// Need to test the size.length because the value is -infinity if no values.
// TODO Must be internationalized.
showInfoNotification('All values are negative so the circle sizes act as if value range was positive which may change the relative sizes.');
// All the values are negative. Plotly will only show circles that are positive. It isn't clear
// there is a perfect solution. This will show the size as if it was positive. For example, if
// it is -100, -200 & -300 then it will use 100, 200, 300. Note the ratio of -100 to -200 (the
// two largest values) is 2x whereas the shifted ones are 300 to 200 which is 1.5. This was
// decided the best of the options considered.
if (Math.abs(max) < minValue) {
// This takes care of case where the max is small so the shifted values will have a very small
// circle size. Force max to be at least minValue (note min negative so shift other way).
// TODO Must be internationalized.
showInfoNotification('Some values are close to zero so the small circle sizes may be a little larger to be visible');
shift = min - minValue;
} else {
shift = min + max;
}
} else if (min >= minValue) {
// There are no values smaller than desired so all circles will be big enough.
// There is no need to shift the range of values.
shift = 0;
} else {
// Some values are too small. Shifting by min would start the range at zero.
// Subtracting minValue shifts more to a larger value so the smallest one
// is the min value to give the smallest circle desired.
// TODO Must be internationalized. Same as message above.
showInfoNotification('Some values are close to zero so the small circle sizes may be a little larger to be visible.');
shift = min - minValue;
if (max > 0 && min < 0) {
// Tell user that there are negative and positive values.
// Unlike all positive that sets the range to effectively start at zero to give users circle size
// that scale proportional to value, it is unclear the correct range for mixed sign values.
// Given this, shift as usual where the small value (large negative) will wind up near 0 and
// have a small circle size. This informs the user of the situation.
// TODO Must be internationalized.
showInfoNotification('There are negative and positive values and this impacts relative circle size.');
}
}
// Change all the sizes by the desired shift. Note the hover is not changed so
// this only impacts the circle size but not the value seen by the user.
size = size.map(size => size - shift);
// This is how much Plotly will scale all circle sizes. (max - shift) is
// the new max value in the range of sizes
// given the shift just done. Dividing my maxCircleSize means the max value
// will have the a circle as big as the largest one desired.
const scaling = (max - shift) / maxCircleSize;

// Per https://plotly.com/javascript/reference/scatter/:
// The opacity of 0.5 makes it possible to see the map even when there is a circle but the hover
Expand All @@ -325,7 +409,9 @@ export default function MapChartComponent() {
color: colors,
opacity: 0.5,
size,
sizemin: 6,
// The best sizemin of a circle is unclear. Given the shifting of sizes above this probably
// should never happen but left to be safe.
sizemin: 5,
sizeref: scaling,
sizemode: 'area'
},
Expand Down Expand Up @@ -377,4 +463,4 @@ export default function MapChartComponent() {
layout={layout}
/>
);
}
}
Loading