performPaint method
Hook for subclasses to implement actual painting.
Implementation
@override
void performPaint(Buffer buffer, Offset offset) {
final viewport = Viewport(
buffer,
Rect(offset.dx, offset.dy, size.width, size.height),
);
if (size.width <= 0 || size.height <= 0) return;
final sparkline = widget as Sparkline;
final (int subWidth, int subHeight) = switch (sparkline.barType) {
ProgressBarType.braille => (2, 4),
ProgressBarType.quads => (2, 2),
ProgressBarType.blocks => switch (sparkline.direction) {
ProgressDirection.bottomToTop ||
ProgressDirection.topToBottom => (1, 8),
ProgressDirection.leftToRight ||
ProgressDirection.rightToLeft => (8, 1),
},
};
final timeFlowsHorizontally =
sparkline.direction == ProgressDirection.bottomToTop ||
sparkline.direction == ProgressDirection.topToBottom;
final maxTimeLength = timeFlowsHorizontally
? size.width * subWidth
: size.height * subHeight;
final crossLength = timeFlowsHorizontally
? size.height * subHeight
: size.width * subWidth;
final data = sparkline.data;
final dataLength = data.length;
final skipCount = dataLength > maxTimeLength
? dataLength - maxTimeLength
: 0;
final paddingCount = maxTimeLength > dataLength
? maxTimeLength - dataLength
: 0;
double getValue(int index) {
if (index < paddingCount) return 0.0;
return data.elementAt(index - paddingCount + skipCount);
}
bool isFilledDot(int x, int y, int dx, int dy) {
int gx = x * subWidth + dx;
int gy = y * subHeight + dy;
final timeIndex = timeFlowsHorizontally ? gx : gy;
final crossIndex = switch (sparkline.direction) {
ProgressDirection.bottomToTop => (size.height * subHeight - 1) - gy,
ProgressDirection.topToBottom => gy,
ProgressDirection.rightToLeft => (size.width * subWidth - 1) - gx,
ProgressDirection.leftToRight => gx,
};
double val = getValue(timeIndex);
double fraction = sparkline.max <= 0 ? 0 : (val / sparkline.max);
double filledCrossUnits = fraction * crossLength;
return crossIndex < filledCrossUnits.round();
}
for (var y = 0; y < size.height; y++) {
for (var x = 0; x < size.width; x++) {
int cellIndex = timeFlowsHorizontally ? x : y;
Color? cellFg = sparkline.style.foreground;
Color? cellBg = sparkline.style.background;
if (sparkline.colorBuilder != null) {
final baseTimeIndex = timeFlowsHorizontally
? (x * subWidth)
: (y * subHeight);
final timeSubUnits = timeFlowsHorizontally ? subWidth : subHeight;
final v0 = getValue(baseTimeIndex);
final v1 = timeSubUnits > 1 ? getValue(baseTimeIndex + 1) : null;
final v2 = timeSubUnits > 2 ? getValue(baseTimeIndex + 2) : null;
final v3 = timeSubUnits > 3 ? getValue(baseTimeIndex + 3) : null;
final colors = sparkline.colorBuilder!(cellIndex, v0, v1, v2, v3);
double cellT = 0.0;
if (size.height > 1 || size.width > 1) {
cellT = switch (sparkline.direction) {
ProgressDirection.bottomToTop =>
(size.height - 1 - y) / (size.height - 1),
ProgressDirection.topToBottom => y / (size.height - 1),
ProgressDirection.leftToRight => x / (size.width - 1),
ProgressDirection.rightToLeft =>
(size.width - 1 - x) / (size.width - 1),
};
}
if (colors.fg != null) {
cellFg = _interpolate(colors.fg, cellT) ?? cellFg;
}
if (colors.bg != null) {
cellBg = _interpolate(colors.bg, cellT) ?? cellBg;
}
}
String char = ' ';
if (sparkline.barType == ProgressBarType.braille) {
int offset = 0;
if (isFilledDot(x, y, 0, 0)) offset |= 1;
if (isFilledDot(x, y, 0, 1)) offset |= 2;
if (isFilledDot(x, y, 0, 2)) offset |= 4;
if (isFilledDot(x, y, 1, 0)) offset |= 8;
if (isFilledDot(x, y, 1, 1)) offset |= 16;
if (isFilledDot(x, y, 1, 2)) offset |= 32;
if (isFilledDot(x, y, 0, 3)) offset |= 64;
if (isFilledDot(x, y, 1, 3)) offset |= 128;
char = String.fromCharCode(0x2800 + offset);
} else if (sparkline.barType == ProgressBarType.quads) {
int offset = 0;
if (isFilledDot(x, y, 0, 0)) offset |= 1;
if (isFilledDot(x, y, 1, 0)) offset |= 2;
if (isFilledDot(x, y, 0, 1)) offset |= 4;
if (isFilledDot(x, y, 1, 1)) offset |= 8;
const quads = [
' ',
'▘',
'▝',
'▀',
'▖',
'▌',
'▞',
'▛',
'▗',
'▚',
'▐',
'▜',
'▄',
'▙',
'▟',
'█',
];
char = quads[offset];
} else {
int filledCount = 0;
for (int dy = 0; dy < subHeight; dy++) {
for (int dx = 0; dx < subWidth; dx++) {
if (isFilledDot(x, y, dx, dy)) filledCount++;
}
}
if (subWidth > 1) {
const blocksHorizontal = [
' ',
'▏',
'▎',
'▍',
'▌',
'▋',
'▊',
'▉',
'█',
];
char = blocksHorizontal[filledCount.clamp(0, 8)];
} else if (subHeight > 1) {
const blocksVertical = [
' ',
'\u2581',
'▂',
'▃',
'▄',
'▅',
'▆',
'▇',
'█',
];
char = blocksVertical[filledCount.clamp(0, 8)];
} else {
char = filledCount > 0 ? '█' : ' ';
}
}
viewport.setAttributes(
x,
y,
char: char,
fg: cellFg?.argb,
bg: cellBg?.argb,
modifiers: sparkline.style.modifiers,
);
}
}
}