Skip to content

Commit

Permalink
Merge pull request #1357 from oss-slu/map_neg
Browse files Browse the repository at this point in the history
Adding negative function to maps
  • Loading branch information
huss authored Oct 8, 2024
2 parents c17b2fe + 72daf14 commit c0611ad
Showing 1 changed file with 87 additions and 8 deletions.
95 changes: 87 additions & 8 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,98 @@ export default function MapChartComponent() {
}
}
}

// 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 +402,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

0 comments on commit c0611ad

Please sign in to comment.