feat: new legends
* merge legendBar and legendDots * use BaseChart to render legends
This commit is contained in:
parent
d84614cb65
commit
7cfa35a418
@ -15,6 +15,7 @@ export default class AggregationChart extends BaseChart {
|
|||||||
this.config.formatTooltipY = (args.tooltipOptions || {}).formatTooltipY;
|
this.config.formatTooltipY = (args.tooltipOptions || {}).formatTooltipY;
|
||||||
this.config.maxSlices = args.maxSlices || 20;
|
this.config.maxSlices = args.maxSlices || 20;
|
||||||
this.config.maxLegendPoints = args.maxLegendPoints || 20;
|
this.config.maxLegendPoints = args.maxLegendPoints || 20;
|
||||||
|
this.config.legendRowHeight = 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
calc() {
|
calc() {
|
||||||
@ -31,17 +32,17 @@ export default class AggregationChart extends BaseChart {
|
|||||||
}).filter(d => { return d[0] >= 0; }); // keep only positive results
|
}).filter(d => { return d[0] >= 0; }); // keep only positive results
|
||||||
|
|
||||||
let totals = allTotals;
|
let totals = allTotals;
|
||||||
if(allTotals.length > maxSlices) {
|
if (allTotals.length > maxSlices) {
|
||||||
// Prune and keep a grey area for rest as per maxSlices
|
// Prune and keep a grey area for rest as per maxSlices
|
||||||
allTotals.sort((a, b) => { return b[0] - a[0]; });
|
allTotals.sort((a, b) => { return b[0] - a[0]; });
|
||||||
|
|
||||||
totals = allTotals.slice(0, maxSlices-1);
|
totals = allTotals.slice(0, maxSlices - 1);
|
||||||
let remaining = allTotals.slice(maxSlices-1);
|
let remaining = allTotals.slice(maxSlices - 1);
|
||||||
|
|
||||||
let sumOfRemaining = 0;
|
let sumOfRemaining = 0;
|
||||||
remaining.map(d => {sumOfRemaining += d[0];});
|
remaining.map(d => { sumOfRemaining += d[0]; });
|
||||||
totals.push([sumOfRemaining, 'Rest']);
|
totals.push([sumOfRemaining, 'Rest']);
|
||||||
this.colors[maxSlices-1] = 'grey';
|
this.colors[maxSlices - 1] = 'grey';
|
||||||
}
|
}
|
||||||
|
|
||||||
s.labels = [];
|
s.labels = [];
|
||||||
@ -62,36 +63,22 @@ export default class AggregationChart extends BaseChart {
|
|||||||
let s = this.state;
|
let s = this.state;
|
||||||
this.legendArea.textContent = '';
|
this.legendArea.textContent = '';
|
||||||
this.legendTotals = s.sliceTotals.slice(0, this.config.maxLegendPoints);
|
this.legendTotals = s.sliceTotals.slice(0, this.config.maxLegendPoints);
|
||||||
|
super.renderLegend(this.legendTotals);
|
||||||
|
}
|
||||||
|
|
||||||
let count = 0;
|
makeLegend(data, index, x_pos, y_pos) {
|
||||||
let y = 0;
|
let formatted = this.config.formatTooltipY ? this.config.formatTooltipY(data) : data;
|
||||||
this.legendTotals.map((d, i) => {
|
|
||||||
let barWidth = 120;
|
return legendDot(
|
||||||
let divisor = Math.floor(
|
x_pos,
|
||||||
(this.width - getExtraWidth(this.measures))/barWidth
|
y_pos,
|
||||||
|
12, // size
|
||||||
|
3, // dot radius
|
||||||
|
this.colors[index], // fill
|
||||||
|
this.state.labels[index], // label
|
||||||
|
formatted, // value
|
||||||
|
null, // base_font_size
|
||||||
|
this.config.truncateLegends // truncate_legends
|
||||||
);
|
);
|
||||||
if (this.legendTotals.length < divisor) {
|
|
||||||
barWidth = this.width/this.legendTotals.length;
|
|
||||||
}
|
|
||||||
if(count > divisor) {
|
|
||||||
count = 0;
|
|
||||||
y += 60;
|
|
||||||
}
|
|
||||||
let x = barWidth * count + 5;
|
|
||||||
let label = this.config.truncateLegends ? truncateString(s.labels[i], barWidth/10) : s.labels[i];
|
|
||||||
let formatted = this.config.formatTooltipY ? this.config.formatTooltipY(d) : d;
|
|
||||||
let dot = legendDot(
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
12,
|
|
||||||
3,
|
|
||||||
this.colors[i],
|
|
||||||
`${label}: ${formatted}`,
|
|
||||||
d,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
this.legendArea.appendChild(dot);
|
|
||||||
count++;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
import BaseChart from './BaseChart';
|
import BaseChart from './BaseChart';
|
||||||
import { dataPrep, zeroDataPrep, getShortenedLabels } from '../utils/axis-chart-utils';
|
import { dataPrep, zeroDataPrep, getShortenedLabels } from '../utils/axis-chart-utils';
|
||||||
import { AXIS_LEGEND_BAR_SIZE } from '../utils/constants';
|
|
||||||
import { getComponent } from '../objects/ChartComponents';
|
import { getComponent } from '../objects/ChartComponents';
|
||||||
import { getOffset, fire } from '../utils/dom';
|
import { getOffset, fire } from '../utils/dom';
|
||||||
import { calcChartIntervals, getIntervalSize, getValueRange, getZeroIndex, scale, getClosestInArray } from '../utils/intervals';
|
import { calcChartIntervals, getIntervalSize, getValueRange, getZeroIndex, scale, getClosestInArray } from '../utils/intervals';
|
||||||
import { floatTwo } from '../utils/helpers';
|
import { floatTwo } from '../utils/helpers';
|
||||||
import { makeOverlay, updateOverlay, legendBar } from '../utils/draw';
|
import { makeOverlay, updateOverlay, legendDot } from '../utils/draw';
|
||||||
import { getTopOffset, getLeftOffset, MIN_BAR_PERCENT_HEIGHT, BAR_CHART_SPACE_RATIO,
|
import {
|
||||||
LINE_CHART_DOT_SIZE } from '../utils/constants';
|
getTopOffset, getLeftOffset, MIN_BAR_PERCENT_HEIGHT, BAR_CHART_SPACE_RATIO,
|
||||||
|
LINE_CHART_DOT_SIZE
|
||||||
|
} from '../utils/constants';
|
||||||
|
|
||||||
export default class AxisChart extends BaseChart {
|
export default class AxisChart extends BaseChart {
|
||||||
constructor(parent, args) {
|
constructor(parent, args) {
|
||||||
@ -44,6 +45,7 @@ export default class AxisChart extends BaseChart {
|
|||||||
this.config.formatTooltipY = options.tooltipOptions.formatTooltipY;
|
this.config.formatTooltipY = options.tooltipOptions.formatTooltipY;
|
||||||
|
|
||||||
this.config.valuesOverPoints = options.valuesOverPoints;
|
this.config.valuesOverPoints = options.valuesOverPoints;
|
||||||
|
this.config.legendRowHeight = 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareData(data=this.data) {
|
prepareData(data=this.data) {
|
||||||
@ -423,29 +425,31 @@ export default class AxisChart extends BaseChart {
|
|||||||
renderLegend() {
|
renderLegend() {
|
||||||
let s = this.data;
|
let s = this.data;
|
||||||
if (s.datasets.length > 1) {
|
if (s.datasets.length > 1) {
|
||||||
this.legendArea.textContent = '';
|
super.renderLegend(s.datasets);
|
||||||
let barWidth = AXIS_LEGEND_BAR_SIZE;
|
|
||||||
s.datasets.map((d, i) => {
|
|
||||||
let rect = legendBar(
|
|
||||||
barWidth * i,
|
|
||||||
'0',
|
|
||||||
this.colors[i],
|
|
||||||
d.name,
|
|
||||||
this.config.truncateLegends);
|
|
||||||
this.legendArea.appendChild(rect);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
makeLegend(data, index, x_pos, y_pos) {
|
||||||
|
return legendDot(
|
||||||
|
x_pos,
|
||||||
|
y_pos + 5, // Extra offset
|
||||||
|
12, // size
|
||||||
|
3, // dot radius
|
||||||
|
this.colors[index], // fill
|
||||||
|
data.name, //label
|
||||||
|
null, // value
|
||||||
|
8.75, // base_font_size
|
||||||
|
this.config.truncateLegends // truncate legends
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Overlay
|
// Overlay
|
||||||
makeOverlay() {
|
makeOverlay() {
|
||||||
if(this.init) {
|
if (this.init) {
|
||||||
this.init = 0;
|
this.init = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(this.overlayGuides) {
|
if (this.overlayGuides) {
|
||||||
this.overlayGuides.forEach(g => {
|
this.overlayGuides.forEach(g => {
|
||||||
let o = g.overlay;
|
let o = g.overlay;
|
||||||
o.parentNode.removeChild(o);
|
o.parentNode.removeChild(o);
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
import SvgTip from '../objects/SvgTip';
|
import SvgTip from '../objects/SvgTip';
|
||||||
import { $, isElementInViewport, getElementContentWidth, isHidden } from '../utils/dom';
|
import { $, isElementInViewport, getElementContentWidth, isHidden } from '../utils/dom';
|
||||||
import { makeSVGContainer, makeSVGDefs, makeSVGGroup, makeText } from '../utils/draw';
|
import { makeSVGContainer, makeSVGDefs, makeSVGGroup, makeText } from '../utils/draw';
|
||||||
import { BASE_MEASURES, getExtraHeight, getExtraWidth, getTopOffset, getLeftOffset,
|
import { LEGEND_ITEM_WIDTH } from '../utils/constants';
|
||||||
INIT_CHART_UPDATE_TIMEOUT, CHART_POST_ANIMATE_TIMEOUT, DEFAULT_COLORS} from '../utils/constants';
|
import {
|
||||||
|
BASE_MEASURES, getExtraHeight, getExtraWidth, getTopOffset, getLeftOffset,
|
||||||
|
INIT_CHART_UPDATE_TIMEOUT, CHART_POST_ANIMATE_TIMEOUT, DEFAULT_COLORS
|
||||||
|
} from '../utils/constants';
|
||||||
import { getColor, isValidColor } from '../utils/colors';
|
import { getColor, isValidColor } from '../utils/colors';
|
||||||
import { runSMILAnimation } from '../utils/animation';
|
import { runSMILAnimation } from '../utils/animation';
|
||||||
import { downloadFile, prepareForExport } from '../utils/export';
|
import { downloadFile, prepareForExport } from '../utils/export';
|
||||||
@ -262,10 +265,29 @@ export default class BaseChart {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderLegend() {}
|
renderLegend(dataset) {
|
||||||
|
this.legendArea.textContent = '';
|
||||||
|
let count = 0;
|
||||||
|
let y = 0;
|
||||||
|
|
||||||
setupNavigation(init=false) {
|
dataset.map((data, index) => {
|
||||||
if(!this.config.isNavigable) return;
|
let divisor = Math.floor(this.width / LEGEND_ITEM_WIDTH);
|
||||||
|
if (count > divisor) {
|
||||||
|
count = 0;
|
||||||
|
y += this.config.legendRowHeight;
|
||||||
|
}
|
||||||
|
let x = LEGEND_ITEM_WIDTH * count;
|
||||||
|
let dot = this.makeLegend(data, index, x, y);
|
||||||
|
this.legendArea.appendChild(dot);
|
||||||
|
count++;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
makeLegend() { }
|
||||||
|
|
||||||
|
|
||||||
|
setupNavigation(init = false) {
|
||||||
|
if (!this.config.isNavigable) return;
|
||||||
|
|
||||||
if(init) {
|
if(init) {
|
||||||
this.bindOverlay();
|
this.bindOverlay();
|
||||||
|
|||||||
@ -65,7 +65,7 @@ export const CHART_POST_ANIMATE_TIMEOUT = 400;
|
|||||||
export const DEFAULT_AXIS_CHART_TYPE = 'line';
|
export const DEFAULT_AXIS_CHART_TYPE = 'line';
|
||||||
export const AXIS_DATASET_CHART_TYPES = ['line', 'bar'];
|
export const AXIS_DATASET_CHART_TYPES = ['line', 'bar'];
|
||||||
|
|
||||||
export const AXIS_LEGEND_BAR_SIZE = 100;
|
export const LEGEND_ITEM_WIDTH = 150;
|
||||||
export const SERIES_LABEL_SPACE_RATIO = 0.6;
|
export const SERIES_LABEL_SPACE_RATIO = 0.6;
|
||||||
|
|
||||||
export const BAR_CHART_SPACE_RATIO = 0.5;
|
export const BAR_CHART_SPACE_RATIO = 0.5;
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { DOT_OVERLAY_SIZE_INCR } from './constants';
|
|||||||
|
|
||||||
export const AXIS_TICK_LENGTH = 6;
|
export const AXIS_TICK_LENGTH = 6;
|
||||||
const LABEL_MARGIN = 4;
|
const LABEL_MARGIN = 4;
|
||||||
const LABEL_MAX_CHARS = 15;
|
const LABEL_MAX_CHARS = 18;
|
||||||
export const FONT_SIZE = 10;
|
export const FONT_SIZE = 10;
|
||||||
const BASE_LINE_COLOR = '#E2E6E9';
|
const BASE_LINE_COLOR = '#E2E6E9';
|
||||||
const FONT_FILL = '#313B44';
|
const FONT_FILL = '#313B44';
|
||||||
@ -223,43 +223,9 @@ export function heatSquare(className, x, y, size, radius, fill='none', data={})
|
|||||||
return createSVG("rect", args);
|
return createSVG("rect", args);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function legendBar(x, y, fill = 'none', label, truncate = true) {
|
export function legendDot(x, y, size, radius, fill = 'none', label, value, font_size = null, truncate = false) {
|
||||||
label = truncate ? truncateString(label, LABEL_MAX_CHARS) : label;
|
|
||||||
|
|
||||||
let args = {
|
|
||||||
className: 'legend-bar',
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: '12px',
|
|
||||||
height: '12px',
|
|
||||||
rx: '2px',
|
|
||||||
ry: '2px',
|
|
||||||
fill: fill
|
|
||||||
};
|
|
||||||
|
|
||||||
let text = createSVG('text', {
|
|
||||||
className: 'legend-dataset-text',
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
dy: (FONT_SIZE) + 'px',
|
|
||||||
dx: (FONT_SIZE * 1.5) + 'px',
|
|
||||||
'font-size': (FONT_SIZE * 1.2) + 'px',
|
|
||||||
'text-anchor': 'start',
|
|
||||||
fill: FONT_FILL,
|
|
||||||
innerHTML: label
|
|
||||||
});
|
|
||||||
|
|
||||||
let group = createSVG('g', {
|
|
||||||
transform: `translate(${x}, ${y})`
|
|
||||||
});
|
|
||||||
group.appendChild(createSVG("rect", args));
|
|
||||||
group.appendChild(text);
|
|
||||||
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function legendDot(x, y, size, radius, fill='none', label, value, truncate=false) {
|
|
||||||
label = truncate ? truncateString(label, LABEL_MAX_CHARS) : label;
|
label = truncate ? truncateString(label, LABEL_MAX_CHARS) : label;
|
||||||
|
if (!font_size) font_size = FONT_SIZE;
|
||||||
|
|
||||||
let args = {
|
let args = {
|
||||||
className: 'legend-dot',
|
className: 'legend-dot',
|
||||||
@ -275,32 +241,38 @@ export function legendDot(x, y, size, radius, fill='none', label, value, truncat
|
|||||||
className: 'legend-dataset-label',
|
className: 'legend-dataset-label',
|
||||||
x: size,
|
x: size,
|
||||||
y: 0,
|
y: 0,
|
||||||
dx: (FONT_SIZE) + 'px',
|
dx: (font_size) + 'px',
|
||||||
dy: (FONT_SIZE/3) + 'px',
|
dy: (font_size / 3) + 'px',
|
||||||
'font-size': (FONT_SIZE * 1.6) + 'px',
|
'font-size': (font_size * 1.6) + 'px',
|
||||||
'text-anchor': 'start',
|
'text-anchor': 'start',
|
||||||
fill: FONT_FILL,
|
fill: FONT_FILL,
|
||||||
innerHTML: label
|
innerHTML: label
|
||||||
});
|
});
|
||||||
|
|
||||||
let textValue = createSVG('text', {
|
let textValue = null;
|
||||||
|
if (value) {
|
||||||
|
textValue = createSVG('text', {
|
||||||
className: 'legend-dataset-value',
|
className: 'legend-dataset-value',
|
||||||
x: size,
|
x: size,
|
||||||
y: FONT_SIZE + 10,
|
y: FONT_SIZE + 10,
|
||||||
dx: (FONT_SIZE) + 'px',
|
dx: (FONT_SIZE) + 'px',
|
||||||
dy: (FONT_SIZE/3) + 'px',
|
dy: (FONT_SIZE / 3) + 'px',
|
||||||
'font-size': (FONT_SIZE * 1.2) + 'px',
|
'font-size': (FONT_SIZE * 1.2) + 'px',
|
||||||
'text-anchor': 'start',
|
'text-anchor': 'start',
|
||||||
fill: FONT_FILL,
|
fill: FONT_FILL,
|
||||||
innerHTML: value
|
innerHTML: value
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let group = createSVG('g', {
|
let group = createSVG('g', {
|
||||||
transform: `translate(${x}, ${y})`
|
transform: `translate(${x}, ${y})`
|
||||||
});
|
});
|
||||||
group.appendChild(createSVG("rect", args));
|
group.appendChild(createSVG("rect", args));
|
||||||
group.appendChild(textLabel);
|
group.appendChild(textLabel);
|
||||||
|
|
||||||
|
if (value && textValue) {
|
||||||
group.appendChild(textValue);
|
group.appendChild(textValue);
|
||||||
|
}
|
||||||
|
|
||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user