@nativescript-community/ui-chart
Version:
A powerful chart / graph plugin, supporting line, bar, pie, radar, bubble, and candlestick charts as well as scaling, panning and animations.
386 lines (385 loc) • 16.2 kB
JavaScript
import { ComponentBase } from './ComponentBase';
import { LegendEntry } from './LegendEntry';
import { Utils } from '../utils/Utils';
import { ColorTemplate } from '../utils/ColorTemplate';
export var LegendForm;
(function (LegendForm) {
/**
* Avoid drawing a form
*/
LegendForm[LegendForm["NONE"] = 0] = "NONE";
/**
* Do not draw the a form, but leave space for it
*/
LegendForm[LegendForm["EMPTY"] = 1] = "EMPTY";
/**
* Use default (default dataset's form to the legend's form)
*/
LegendForm[LegendForm["DEFAULT"] = 2] = "DEFAULT";
/**
* Draw a square
*/
LegendForm[LegendForm["SQUARE"] = 3] = "SQUARE";
/**
* Draw a circle
*/
LegendForm[LegendForm["CIRCLE"] = 4] = "CIRCLE";
/**
* Draw a horizontal line
*/
LegendForm[LegendForm["LINE"] = 5] = "LINE";
})(LegendForm || (LegendForm = {}));
export var LegendHorizontalAlignment;
(function (LegendHorizontalAlignment) {
LegendHorizontalAlignment[LegendHorizontalAlignment["LEFT"] = 0] = "LEFT";
LegendHorizontalAlignment[LegendHorizontalAlignment["CENTER"] = 1] = "CENTER";
LegendHorizontalAlignment[LegendHorizontalAlignment["RIGHT"] = 2] = "RIGHT";
})(LegendHorizontalAlignment || (LegendHorizontalAlignment = {}));
export var LegendVerticalAlignment;
(function (LegendVerticalAlignment) {
LegendVerticalAlignment[LegendVerticalAlignment["TOP"] = 0] = "TOP";
LegendVerticalAlignment[LegendVerticalAlignment["CENTER"] = 1] = "CENTER";
LegendVerticalAlignment[LegendVerticalAlignment["BOTTOM"] = 2] = "BOTTOM";
})(LegendVerticalAlignment || (LegendVerticalAlignment = {}));
export var LegendOrientation;
(function (LegendOrientation) {
LegendOrientation[LegendOrientation["HORIZONTAL"] = 0] = "HORIZONTAL";
LegendOrientation[LegendOrientation["VERTICAL"] = 1] = "VERTICAL";
})(LegendOrientation || (LegendOrientation = {}));
export var LegendDirection;
(function (LegendDirection) {
LegendDirection[LegendDirection["LEFT_TO_RIGHT"] = 0] = "LEFT_TO_RIGHT";
LegendDirection[LegendDirection["RIGHT_TO_LEFT"] = 1] = "RIGHT_TO_LEFT";
})(LegendDirection || (LegendDirection = {}));
/**
* Class representing the legend of the chart. The legend will contain one entry
* per color and DataSet. Multiple colors in one DataSet are grouped together.
* The legend object is NOT available before setting data to the chart.
*
*/
export class Legend extends ComponentBase {
/**
* Constructor. Provide entries for the legend.
*
* @param entries
*/
constructor(entries) {
super();
/**
* The legend entries array
*/
this.entries = [];
/**
* Are the legend labels/colors a custom value or auto calculated? If false,
* then it's auto, if true, then custom. default false (automatic legend)
*/
this.mIsLegendCustom = false;
this.horizontalAlignment = LegendHorizontalAlignment.LEFT;
this.verticalAlignment = LegendVerticalAlignment.BOTTOM;
this.orientation = LegendOrientation.HORIZONTAL;
this.drawInside = false;
/**
* the text direction for the legend
*/
this.direction = LegendDirection.LEFT_TO_RIGHT;
/**
* the shape/form the legend colors are drawn in
*/
this.form = LegendForm.SQUARE;
/**
* the size of the legend forms/shapes
*/
this.formSize = 8;
/**
* the size of the legend forms/shapes
*/
this.formLineWidth = 3;
/**
* Line dash path effect used for shapes that consist of lines.
*/
this.formLineDashEffect = null;
/**
* the space between the legend entries on a horizontal axis, default 6f
*/
this.xEntrySpace = 6;
/**
* the space between the legend entries on a vertical axis, default 5f
*/
this.yEntrySpace = 0;
/**
* the space between the legend entries on a vertical axis, default 2f
* private float this.mYEntrySpace = 2f; /** the space between the form and the
* actual label/text
*/
this.formToTextSpace = 5;
/**
* the space that should be left between stacked forms
*/
this.stackSpace = 3;
/**
* The maximum relative size out of the whole chart view. / If the legend is
* to the right/left of the chart, then this affects the width of the
* legend. / If the legend is to the top/bottom of the chart, then this
* affects the height of the legend. / If the legend is the center of the
* piechart, then this defines the size of the rectangular bounds out of the
* size of the "hole". / default: 0.95f (95%)
*/
this.maxSizePercent = 0.95;
/**
* the total width of the legend (needed width space)
*/
this.mNeededWidth = 0;
/**
* the total height of the legend (needed height space)
*/
this.mNeededHeight = 0;
this.mTextHeightMax = 0;
this.mTextWidthMax = 0;
/**
* Should the legend word wrap? / this is currently supported only for:
* BelowChartLeft, BelowChartRight, BelowChartCenter. / note that word
* wrapping a legend takes a toll on performance. / you may want to set
* maxSizePercent when word wrapping, to set the point where the text wraps.
* / default: false
*/
this.wordWrapEnabled = false;
this.calculatedLabelSizes = [];
this.calculatedLabelBreakPoints = [];
this.calculatedLineSizes = [];
this.enabled = false;
this.textSize = 10;
this.xOffset = 5;
this.yOffset = 5;
if (entries) {
this.entries = entries;
}
}
/**
* returns the maximum length in pixels across all legend labels + formsize
* + formtotextspace
*
* @param p the paint object used for rendering the text
* @return
*/
getMaximumEntryWidth(p) {
let max = 0;
let maxFormSize = 0;
const formToTextSpace = this.formToTextSpace;
for (const entry of this.entries) {
const formSize = isNaN(entry.formSize) ? this.formSize : entry.formSize;
if (formSize > maxFormSize)
maxFormSize = formSize;
const label = entry.label;
if (!label)
continue;
const length = Utils.calcTextWidth(p, label);
if (length > max)
max = length;
}
return max + maxFormSize + formToTextSpace;
}
/**
* returns the maximum height in pixels across all legend labels
*
* @param p the paint object used for rendering the text
* @return
*/
getMaximumEntryHeight(p) {
let max = 0;
for (const entry of this.entries) {
const label = entry.label;
if (!label)
continue;
const length = Utils.calcTextHeight(p, label);
if (length > max)
max = length;
}
return max;
}
/**
* Entries that will be appended to the end of the auto calculated
* entries after calculating the legend.
* (if the legend has already been calculated, you will need to call notifyDataSetChanged()
* to let the changes take effect)
*/
setExtra(colors, labels) {
const entries = [];
for (let i = 0; i < Math.min(colors.length, labels.length); i++) {
const entry = new LegendEntry(labels[i], colors[i]);
// entry.formColor = colors[i];
// entry.label = labels[i];
if (entry.formColor === ColorTemplate.COLOR_SKIP || !entry.formColor)
entry.form = LegendForm.NONE;
else if (entry.formColor === ColorTemplate.COLOR_NONE)
entry.form = LegendForm.EMPTY;
entries.push(entry);
}
this.extraEntries = entries;
}
/**
* Sets a custom legend's entries array.
* * A null label will start a group.
* This will disable the feature that automatically calculates the legend
* entries from the datasets.
* Call resetCustom() to re-enable automatic calculation (and then
* notifyDataSetChanged() is needed to auto-calculate the legend again)
*/
setCustom(entries) {
this.entries = entries;
this.mIsLegendCustom = true;
}
/**
* Calling this will disable the custom legend entries (set by
* setCustom(...)). Instead, the entries will again be calculated
* automatically (after notifyDataSetChanged() is called).
*/
resetCustom() {
this.mIsLegendCustom = false;
}
/**
* @return true if a custom legend entries has been set default
* false (automatic legend)
*/
isLegendCustom() {
return this.mIsLegendCustom;
}
/**
* Calculates the dimensions of the Legend. This includes the maximum width
* and height of a single entry, as well as the total width and height of
* the Legend.
*
* @param labelpaint
*/
calculateDimensions(labelpaint, viewPortHandler) {
const defaultFormSize = this.formSize;
const stackSpace = this.stackSpace;
const formToTextSpace = this.formToTextSpace;
const xEntrySpace = this.xEntrySpace;
const yEntrySpace = this.yEntrySpace;
const wordWrapEnabled = this.wordWrapEnabled;
const entries = this.entries;
const entryCount = entries.length;
this.mTextWidthMax = this.getMaximumEntryWidth(labelpaint);
this.mTextHeightMax = this.getMaximumEntryHeight(labelpaint);
switch (this.orientation) {
case LegendOrientation.VERTICAL: {
let maxWidth = 0, maxHeight = 0, width = 0;
const labelLineHeight = Utils.getLineHeight(labelpaint);
let wasStacked = false;
for (let i = 0; i < entryCount; i++) {
const e = entries[i];
const drawingForm = e.form !== LegendForm.NONE;
const formSize = isNaN(e.formSize) ? defaultFormSize : e.formSize;
const label = e.label;
if (!wasStacked)
width = 0;
if (drawingForm) {
if (wasStacked)
width += stackSpace;
width += formSize;
}
// grouped forms have null labels
if (label) {
// make a step to the left
if (drawingForm && !wasStacked)
width += formToTextSpace;
else if (wasStacked) {
maxWidth = Math.max(maxWidth, width);
maxHeight += labelLineHeight + yEntrySpace;
width = 0;
wasStacked = false;
}
width += Utils.calcTextWidth(labelpaint, label);
if (i < entryCount - 1)
maxHeight += labelLineHeight + yEntrySpace;
}
else {
wasStacked = true;
width += formSize;
if (i < entryCount - 1)
width += stackSpace;
}
maxWidth = Math.max(maxWidth, width);
}
this.mNeededWidth = maxWidth;
this.mNeededHeight = maxHeight;
break;
}
case LegendOrientation.HORIZONTAL: {
const labelLineHeight = Utils.getLineHeight(labelpaint);
const labelLineSpacing = Utils.getLineSpacing(labelpaint) + yEntrySpace;
const contentWidth = viewPortHandler.contentRect.width() * this.maxSizePercent;
// Start calculating layout
let maxLineWidth = 0;
let currentLineWidth = 0;
let requiredWidth = 0;
let stackedStartIndex = -1;
this.calculatedLabelBreakPoints = [];
this.calculatedLabelSizes = [];
this.calculatedLineSizes = [];
for (let i = 0; i < entryCount; i++) {
const e = entries[i];
const drawingForm = e.form !== LegendForm.NONE;
const formSize = isNaN(e.formSize) ? defaultFormSize : e.formSize;
const label = e.label;
this.calculatedLabelBreakPoints.push(false);
if (stackedStartIndex === -1) {
// we are not stacking, so required width is for this label
// only
requiredWidth = 0;
}
else {
// add the spacing appropriate for stacked labels/forms
requiredWidth += stackSpace;
}
// grouped forms have null labels
if (label) {
this.calculatedLabelSizes.push(Utils.calcTextSize(labelpaint, label));
requiredWidth += drawingForm ? formToTextSpace + formSize : 0;
requiredWidth += this.calculatedLabelSizes[i].width;
}
else {
this.calculatedLabelSizes.push({ x: 0, y: 0 });
requiredWidth += drawingForm ? formSize : 0;
if (stackedStartIndex === -1) {
// mark this index as we might want to break here later
stackedStartIndex = i;
}
}
if (label || i === entryCount - 1) {
const requiredSpacing = currentLineWidth === 0 ? 0 : xEntrySpace;
if (!wordWrapEnabled || // No word wrapping, it must fit.
// The line is empty, it must fit
currentLineWidth === 0 ||
// It simply fits
contentWidth - currentLineWidth >= requiredSpacing + requiredWidth) {
// Expand current line
currentLineWidth += requiredSpacing + requiredWidth;
}
else {
// It doesn't fit, we need to wrap a line
// Add current line size to array
this.calculatedLineSizes.push({ x: currentLineWidth, y: labelLineHeight });
maxLineWidth = Math.max(maxLineWidth, currentLineWidth);
// Start a new line
this.calculatedLabelBreakPoints[stackedStartIndex > -1 ? stackedStartIndex : i] = true;
currentLineWidth = requiredWidth;
}
if (i === entryCount - 1) {
// Add last line size to array
this.calculatedLineSizes.push({ x: currentLineWidth, y: labelLineHeight });
maxLineWidth = Math.max(maxLineWidth, currentLineWidth);
}
}
stackedStartIndex = label ? -1 : stackedStartIndex;
}
this.mNeededWidth = maxLineWidth;
this.mNeededHeight = labelLineHeight * this.calculatedLineSizes.length + labelLineSpacing * (this.calculatedLineSizes.length === 0 ? 0 : this.calculatedLineSizes.length - 1);
break;
}
}
this.mNeededHeight += this.yOffset;
this.mNeededWidth += this.xOffset;
}
}
//# sourceMappingURL=Legend.js.map