Skia - Basic Charts and Data Visualization
This article demonstrates how to create basic data visualizations with Skia, including line charts, bar charts, and scatter plots. We'll cover the fundamental drawing operations and show how to apply them to real data visualization tasks.
Setting Up a Basic Canvas
Before we can draw charts, we need to set up a canvas. Here's a basic setup for web applications using CanvasKit:
// Initialize CanvasKit
const ck = await CanvasKitInit({
locateFile: (file) => 'https://unpkg.com/canvaskit-wasm@latest/bin/' + file
});
// Create surface
const surface = ck.MakeCanvasSurface('chart-canvas');
const canvas = surface.getCanvas();
// Set up dimensions
const width = 800;
const height = 600;
const padding = 50; // Padding around the chart area
Drawing Primitives
Skia provides several basic drawing primitives that we'll use to build charts:
Lines
Draw lines using drawLine():
const paint = new ck.Paint();
paint.setColor(ck.Color(0, 0, 0, 1.0)); // Black
paint.setStrokeWidth(2);
paint.setStyle(ck.PaintStyle.Stroke);
// Draw a line from (x1, y1) to (x2, y2)
canvas.drawLine(x1, y1, x2, y2, paint);
Rectangles
Draw rectangles using drawRect():
const paint = new ck.Paint();
paint.setColor(ck.Color(100, 150, 200, 1.0)); // Blue
paint.setStyle(ck.PaintStyle.Fill);
// Draw a filled rectangle
const rect = ck.LTRBRect(left, top, right, bottom);
canvas.drawRect(rect, paint);
Paths
Create complex shapes using paths:
const path = new ck.Path();
path.moveTo(x1, y1);
path.lineTo(x2, y2);
path.lineTo(x3, y3);
path.close();
canvas.drawPath(path, paint);
Creating a Line Chart
A line chart connects data points with lines. Here's how to create one:
function drawLineChart(canvas, data, width, height, padding) {
const ck = canvas.getCanvasKit();
// Calculate chart area
const chartWidth = width - 2 * padding;
const chartHeight = height - 2 * padding;
const chartLeft = padding;
const chartTop = padding;
const chartRight = width - padding;
const chartBottom = height - padding;
// Find data range
const xValues = data.map(d => d.x);
const yValues = data.map(d => d.y);
const xMin = Math.min(...xValues);
const xMax = Math.max(...xValues);
const yMin = Math.min(...yValues);
const yMax = Math.max(...yValues);
// Scale function to map data to screen coordinates
const scaleX = (x) => chartLeft + ((x - xMin) / (xMax - xMin)) * chartWidth;
const scaleY = (y) => chartBottom - ((y - yMin) / (yMax - yMin)) * chartHeight;
// Draw axes
const axisPaint = new ck.Paint();
axisPaint.setColor(ck.Color(0, 0, 0, 1.0));
axisPaint.setStrokeWidth(2);
axisPaint.setStyle(ck.PaintStyle.Stroke);
// X-axis
canvas.drawLine(chartLeft, chartBottom, chartRight, chartBottom, axisPaint);
// Y-axis
canvas.drawLine(chartLeft, chartTop, chartLeft, chartBottom, axisPaint);
// Draw data line
const linePaint = new ck.Paint();
linePaint.setColor(ck.Color(0, 100, 200, 1.0)); // Blue
linePaint.setStrokeWidth(3);
linePaint.setStyle(ck.PaintStyle.Stroke);
const path = new ck.Path();
for (let i = 0; i < data.length; i++) {
const x = scaleX(data[i].x);
const y = scaleY(data[i].y);
if (i === 0) {
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
}
canvas.drawPath(path, linePaint);
// Draw data points
const pointPaint = new ck.Paint();
pointPaint.setColor(ck.Color(0, 100, 200, 1.0));
pointPaint.setStyle(ck.PaintStyle.Fill);
for (const point of data) {
const x = scaleX(point.x);
const y = scaleY(point.y);
canvas.drawCircle(x, y, 4, pointPaint);
}
}
Creating a Bar Chart
Bar charts represent data using rectangular bars:
function drawBarChart(canvas, data, width, height, padding) {
const ck = canvas.getCanvasKit();
// Calculate chart area
const chartWidth = width - 2 * padding;
const chartHeight = height - 2 * padding;
const chartLeft = padding;
const chartBottom = height - padding;
// Find data range
const values = data.map(d => d.value);
const maxValue = Math.max(...values);
const barWidth = chartWidth / data.length;
const barSpacing = barWidth * 0.2; // 20% spacing between bars
const actualBarWidth = barWidth - barSpacing;
// Draw bars
data.forEach((item, index) => {
const barHeight = (item.value / maxValue) * chartHeight;
const barLeft = chartLeft + index * barWidth + barSpacing / 2;
const barTop = chartBottom - barHeight;
const barRight = barLeft + actualBarWidth;
const barBottom = chartBottom;
const barPaint = new ck.Paint();
barPaint.setColor(ck.Color(100, 150, 200, 1.0));
barPaint.setStyle(ck.PaintStyle.Fill);
const rect = ck.LTRBRect(barLeft, barTop, barRight, barBottom);
canvas.drawRect(rect, barPaint);
});
// Draw axes
const axisPaint = new ck.Paint();
axisPaint.setColor(ck.Color(0, 0, 0, 1.0));
axisPaint.setStrokeWidth(2);
axisPaint.setStyle(ck.PaintStyle.Stroke);
canvas.drawLine(chartLeft, chartBottom, chartLeft + chartWidth, chartBottom, axisPaint);
canvas.drawLine(chartLeft, chartBottom, chartLeft, padding, axisPaint);
}
Creating a Scatter Plot
Scatter plots show relationships between two variables:
function drawScatterPlot(canvas, data, width, height, padding) {
const ck = canvas.getCanvasKit();
// Calculate chart area
const chartWidth = width - 2 * padding;
const chartHeight = height - 2 * padding;
const chartLeft = padding;
const chartTop = padding;
const chartRight = width - padding;
const chartBottom = height - padding;
// Find data range
const xValues = data.map(d => d.x);
const yValues = data.map(d => d.y);
const xMin = Math.min(...xValues);
const xMax = Math.max(...xValues);
const yMin = Math.min(...yValues);
const yMax = Math.max(...yValues);
// Scale functions
const scaleX = (x) => chartLeft + ((x - xMin) / (xMax - xMin)) * chartWidth;
const scaleY = (y) => chartBottom - ((y - yMin) / (yMax - yMin)) * chartHeight;
// Draw axes
const axisPaint = new ck.Paint();
axisPaint.setColor(ck.Color(0, 0, 0, 1.0));
axisPaint.setStrokeWidth(2);
axisPaint.setStyle(ck.PaintStyle.Stroke);
canvas.drawLine(chartLeft, chartBottom, chartRight, chartBottom, axisPaint);
canvas.drawLine(chartLeft, chartTop, chartLeft, chartBottom, axisPaint);
// Draw data points
const pointPaint = new ck.Paint();
pointPaint.setColor(ck.Color(200, 50, 50, 1.0)); // Red
pointPaint.setStyle(ck.PaintStyle.Fill);
data.forEach(point => {
const x = scaleX(point.x);
const y = scaleY(point.y);
canvas.drawCircle(x, y, 5, pointPaint);
});
}
Adding Labels and Titles
Charts need labels and titles to be useful:
function drawText(canvas, text, x, y, fontSize = 16, color = [0, 0, 0, 1.0]) {
const ck = canvas.getCanvasKit();
const font = new ck.Font(null, fontSize);
const paint = new ck.Paint();
paint.setColor(ck.Color(...color));
paint.setAntiAlias(true);
canvas.drawText(text, x, y, font, paint);
}
// Draw title
drawText(canvas, "Sales Over Time", width / 2, 30, 24, [0, 0, 0, 1.0]);
// Draw axis labels
drawText(canvas, "Time", width / 2, height - 10, 14);
drawText(canvas, "Sales", 10, height / 2, 14);
Styling and Customization
Skia provides extensive styling options:
Colors
// RGB color
paint.setColor(ck.Color(255, 0, 0, 1.0)); // Red
// HSB color
paint.setColor(ck.Color(0, 1.0, 1.0, 1.0, ck.ColorSpace.SRGB)); // Red in HSB
// With alpha
paint.setColor(ck.Color(255, 0, 0, 0.5)); // Semi-transparent red
Gradients
const colors = [
ck.Color(100, 150, 200, 1.0),
ck.Color(200, 100, 150, 1.0)
];
const positions = [0.0, 1.0];
const shader = ck.Shader.MakeLinearGradient(
[0, 0],
[0, height],
colors,
positions,
ck.TileMode.Clamp
);
paint.setShader(shader);
Strokes and Fills
// Filled shape
paint.setStyle(ck.PaintStyle.Fill);
// Outlined shape
paint.setStyle(ck.PaintStyle.Stroke);
paint.setStrokeWidth(3);
// Filled and outlined
paint.setStyle(ck.PaintStyle.StrokeAndFill);
Coordinate System Transformations
Transform the coordinate system for easier drawing:
// Save current transformation
canvas.save();
// Translate to center
canvas.translate(width / 2, height / 2);
// Scale
canvas.scale(2, 2); // Make everything 2x bigger
// Rotate
canvas.rotate(45); // Rotate 45 degrees
// Draw (now in transformed coordinates)
canvas.drawCircle(0, 0, 50, paint);
// Restore transformation
canvas.restore();
Example: Complete Line Chart
Here's a complete example that puts it all together:
async function createLineChart() {
const ck = await CanvasKitInit({
locateFile: (file) => 'https://unpkg.com/canvaskit-wasm@latest/bin/' + file
});
const surface = ck.MakeCanvasSurface('chart');
const canvas = surface.getCanvas();
const width = 800;
const height = 600;
const padding = 60;
// Sample data
const data = [
{ x: 0, y: 10 },
{ x: 1, y: 25 },
{ x: 2, y: 15 },
{ x: 3, y: 30 },
{ x: 4, y: 20 },
{ x: 5, y: 35 }
];
// Draw the chart
drawLineChart(canvas, data, width, height, padding);
// Draw title
const font = new ck.Font(null, 24);
const titlePaint = new ck.Paint();
titlePaint.setColor(ck.Color(0, 0, 0, 1.0));
titlePaint.setAntiAlias(true);
canvas.drawText("Sample Data", width / 2 - 80, 30, font, titlePaint);
surface.flush();
}
Best Practices
When creating charts with Skia:
- Calculate ranges first - Determine data ranges before drawing
- Use consistent padding - Maintain margins for readability
- Reuse Paint objects - Create paint objects once and reuse them
- Group drawing operations - Draw similar elements together
- Handle edge cases - Empty data, single points, etc.
- Add labels - Always include axis labels and titles
- Consider accessibility - Use colors that work for colorblind users
Performance Tips
For better performance:
- Reuse objects - Don't create new Paint/Path objects in loops
- Batch operations - Group similar drawing commands
- Use hardware acceleration - Ensure GPU acceleration is enabled
- Minimize redraws - Only redraw when data changes
- Optimize paths - Simplify complex paths when possible
Next Steps
Now that you can create basic charts, the next article will explore advanced techniques including animations, interactions, and more complex visualization types.
Conclusion
Skia provides the building blocks needed to create custom data visualizations with precise control and excellent performance. By combining basic drawing primitives with coordinate transformations and styling, you can create any chart type you need.
For related topics:
- Skia - Introduction and Overview - Learn about Skia fundamentals
- Skia - Advanced Data Visualization Techniques - Advanced visualization techniques
- Skia Series Index - Series overview
- Coding Projects - Overview of coding projects
This article is part of the Skia Framework series. Previous: Skia - Introduction and Overview. Next: Skia - Advanced Data Visualization Techniques.