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(heatmap): compute nice legend items from color scale #1273

Merged
merged 17 commits into from
Aug 16, 2021
Merged
Changes from 1 commit
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
Prev Previous commit
Next Next commit
refactor: rename color scale ticks to start bands
markov00 committed Aug 11, 2021

Unverified

This user has not yet uploaded their public signing key.
commit 494d41f45c031979f35c47cd22ce6f46a2b00718
Original file line number Diff line number Diff line change
@@ -16,21 +16,21 @@ const EMPTY_LEGEND: LegendItem[] = [];
/** @internal */
export const computeLegendSelector = createCustomCachedSelector(
[getSpecOrNull, getColorScale, getDeselectedSeriesSelector],
(spec, { ticks, scale }, deselectedDataSeries): LegendItem[] => {
(spec, { bands, scale }, deselectedDataSeries): LegendItem[] => {
if (spec === null) {
return EMPTY_LEGEND;
}

return ticks.map(({ tick, formattedTick }, i) => {
const color = scale(tick);
return bands.map(({ start, formattedStart }, i) => {
const color = scale(start);
const seriesIdentifier = {
key: String(tick),
specId: String(tick),
key: String(start),
specId: String(start),
};

return {
color,
label: `>${i === 0 ? '=' : ''} ${formattedTick}`,
label: `>${i === 0 ? '=' : ''} ${formattedStart}`,
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if we can use a symbol

Copy link
Member Author

Choose a reason for hiding this comment

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

done in f19f14b

Copy link
Member Author

Choose a reason for hiding this comment

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

The logic has changed a bit and now every item is GTE the value in the legend
4228efc

seriesIdentifiers: [seriesIdentifier],
isSeriesHidden: deselectedDataSeries.some((dataSeries) => dataSeries.key === seriesIdentifier.key),
isToggleable: true,
Original file line number Diff line number Diff line change
@@ -35,19 +35,19 @@ export const geometries = createCustomCachedSelector(
chartDimensions,
settingSpec,
heatmapTable,
{ ticks, scale: colorScale },
{ bands, scale: colorScale },
deselectedSeries,
gridHeightParams,
): ShapeViewModel => {
const deselectedTicks = new Set(
const deselectedRanges = new Set(
deselectedSeries.map(({ specId }) => {
return Number(specId);
}),
);
const visibilityFilterRanges = ticks.reduce<Array<[number, number]>>((acc, { tick }, i) => {
if (deselectedTicks.has(tick)) {
const rangeEnd = ticks.length === i + 1 ? Infinity : ticks[i + 1].tick;
acc.push([tick, rangeEnd]);
const visibilityFilterRanges = bands.reduce<Array<[number, number]>>((acc, { start }, i) => {
if (deselectedRanges.has(start)) {
const rangeEnd = bands.length === i + 1 ? Infinity : bands[i + 1].start;
acc.push([start, rangeEnd]);
}
return acc;
}, []);
Original file line number Diff line number Diff line change
@@ -20,14 +20,15 @@ import {

import { ScaleType } from '../../../../scales/constants';
import { createCustomCachedSelector } from '../../../../state/create_selector';
import { identity } from '../../../../utils/common';
import { HeatmapSpec } from '../../specs/heatmap';
import { HeatmapTable } from './compute_chart_dimensions';
import { getHeatmapSpecSelector } from './get_heatmap_spec';
import { getHeatmapTableSelector } from './get_heatmap_table';

type ScaleModelType<S> = {
scale: S;
ticks: number[];
bands: Array<{ start: number }>;
};

type ScaleLinearType = ScaleModelType<ScaleLinear<string, string>>;
@@ -54,28 +55,25 @@ const SCALE_TYPE_TO_SCALE_FN = {
export const getColorScale = createCustomCachedSelector(
[getHeatmapSpecSelector, getHeatmapTableSelector],
(spec, heatmapTable) => {
const { scale, ticks } = SCALE_TYPE_TO_SCALE_FN[spec.colorScale ?? ScaleType.Linear](spec, heatmapTable);
const { scale, bands } = SCALE_TYPE_TO_SCALE_FN[spec.colorScale ?? ScaleType.Linear](spec, heatmapTable);
markov00 marked this conversation as resolved.
Show resolved Hide resolved
return {
scale,
...dedupTicks(ticks, spec),
bands: dedupBands(bands, spec),
};
},
);

function dedupTicks(ticks: number[], spec: HeatmapSpec) {
return ticks.reduce<{ uniqueTicks: string[]; ticks: Array<{ tick: number; formattedTick: string }> }>(
(acc, curr) => {
const formattedTick = `${spec.valueFormatter ? spec.valueFormatter(curr) : curr}`;
if (acc.uniqueTicks.includes(formattedTick)) {
return acc;
}
return {
uniqueTicks: [...acc.uniqueTicks, formattedTick],
ticks: [...acc.ticks, { tick: curr, formattedTick }],
};
function dedupBands(bands: Array<{ start: number }>, spec: HeatmapSpec) {
const formatter = spec.valueFormatter ?? identity;
const bandsWithFormattedStarts = bands.reduce<Map<string, { start: number; formattedStart: string }>>(
(acc, { start }) => {
const formattedStart = `${formatter(start)}`;
acc.set(formattedStart, { start, formattedStart });
return acc;
},
{ uniqueTicks: [], ticks: [] },
new Map(),
);
return [...bandsWithFormattedStarts.values()];
}

monfera marked this conversation as resolved.
Show resolved Hide resolved
function getQuantizedScale(spec: HeatmapSpec, heatmapTable: HeatmapTable): ScaleQuantizeType {
@@ -85,38 +83,38 @@ function getQuantizedScale(spec: HeatmapSpec, heatmapTable: HeatmapTable): Scale
// we use the data extent or only the first two values in the `ranges` prop
const scale = scaleQuantize<string>().domain(domain).range(colors);
// quantize scale works as the linear one, we should manually
// compute the ticks corresponding to the quantized segments
// compute the start of each band corresponding to the quantized segments
const numOfSegments = colors.length;
const interval = (domain[1] - domain[0]) / numOfSegments;
monfera marked this conversation as resolved.
Show resolved Hide resolved
const ticks = colors.map((d, i) => domain[0] + interval * i);
const bands = colors.map((color, i) => ({ start: domain[0] + interval * i }));

return {
scale,
ticks,
bands,
};
}

function getQuantileScale(spec: HeatmapSpec, heatmapTable: HeatmapTable): ScaleQuantileType {
const colors = spec.colors ?? DEFAULT_COLORS;
const domain = heatmapTable.table.map(({ value }) => value);
monfera marked this conversation as resolved.
Show resolved Hide resolved
const scale = scaleQuantile<string>().domain(domain).range(colors);
// the ticks array should contain all quantiles + the minimum value
const ticks = [...new Set([heatmapTable.extent[0], ...scale.quantiles()])];
// the bands array should contain all quantiles + the minimum value
const bands = [...new Set([heatmapTable.extent[0], ...scale.quantiles()])].map((start) => ({ start }));
return {
scale,
ticks,
bands,
};
}

function getThresholdScale(spec: HeatmapSpec, heatmapTable: HeatmapTable): ScaleThresholdType {
const colors = spec.colors ?? DEFAULT_COLORS;
const domain = spec.ranges ?? heatmapTable.extent;
const scale = scaleThreshold<number, string>().domain(domain).range(colors);
// the ticks array should contain all the thresholds + the minimum value
const ticks = [...new Set([heatmapTable.extent[0], ...domain])];
// the start band array should contain all the thresholds + the minimum value
const bands = [...new Set([heatmapTable.extent[0], ...domain])].map((start) => ({ start }));
return {
scale,
ticks,
bands,
};
}

@@ -125,9 +123,9 @@ function getLinearScale(spec: HeatmapSpec, heatmapTable: HeatmapTable): ScaleLin
const colors = spec.colors ?? DEFAULT_COLORS;
const scale = scaleLinear<string>().domain(domain).interpolate(interpolateHcl).range(colors).clamp(true);
// adding initial and final range/extent value if they are rounded values.
const ticks = [...new Set([domain[0], ...scale.ticks(6)])];
const bands = [...new Set([domain[0], ...scale.ticks(6)])].map((start) => ({ start }));
return {
scale,
ticks,
bands,
};
}