d3-charts-viz-library
Version:
A comprehensive D3-based chart library with customizable charts and graphs
427 lines (363 loc) • 10.7 kB
JavaScript
// Utility functions for the D3 Chart Library
/**
* Data processing utilities
*/
export const DataUtils = {
/**
* Convert CSV data to chart format
*/
parseCSV: function(csvString, xColumn, yColumn, labelColumn = null) {
const lines = csvString.trim().split('\n');
const headers = lines[0].split(',').map(h => h.trim());
const xIndex = headers.indexOf(xColumn);
const yIndex = headers.indexOf(yColumn);
const labelIndex = labelColumn ? headers.indexOf(labelColumn) : -1;
if (xIndex === -1 || yIndex === -1) {
throw new Error('Column not found in CSV data');
}
return lines.slice(1).map(line => {
const values = line.split(',').map(v => v.trim());
const dataPoint = {
x: isNaN(values[xIndex]) ? values[xIndex] : +values[xIndex],
y: isNaN(values[yIndex]) ? values[yIndex] : +values[yIndex]
};
if (labelIndex !== -1) {
dataPoint.label = values[labelIndex];
}
return dataPoint;
});
},
/**
* Generate sample data for testing
*/
generateSampleData: function(type = 'linear', count = 50) {
const data = [];
switch (type) {
case 'linear':
for (let i = 0; i < count; i++) {
data.push({
x: i,
y: i * 2 + Math.random() * 10,
label: `Point ${i}`
});
}
break;
case 'sine':
for (let i = 0; i < count; i++) {
const x = (i / count) * 4 * Math.PI;
data.push({
x: x,
y: Math.sin(x) * 50 + 50,
label: `Point ${i}`
});
}
break;
case 'random':
for (let i = 0; i < count; i++) {
data.push({
x: Math.random() * 100,
y: Math.random() * 100,
label: `Point ${i}`
});
}
break;
case 'categories':
const categories = ['A', 'B', 'C', 'D', 'E'];
return categories.map(cat => ({
label: cat,
value: Math.floor(Math.random() * 100) + 10
}));
}
return data;
},
/**
* Normalize data to a specific range
*/
normalize: function(data, field = 'y', min = 0, max = 1) {
const values = data.map(d => d[field]);
const dataMin = Math.min(...values);
const dataMax = Math.max(...values);
const range = dataMax - dataMin;
return data.map(d => ({
...d,
[field]: ((d[field] - dataMin) / range) * (max - min) + min
}));
},
/**
* Group data by a specific field
*/
groupBy: function(data, field) {
return data.reduce((groups, item) => {
const key = item[field];
if (!groups[key]) {
groups[key] = [];
}
groups[key].push(item);
return groups;
}, {});
},
/**
* Calculate moving average
*/
movingAverage: function(data, window = 5, field = 'y') {
const result = [];
for (let i = 0; i < data.length; i++) {
const start = Math.max(0, i - Math.floor(window / 2));
const end = Math.min(data.length, i + Math.ceil(window / 2));
const subset = data.slice(start, end);
const average = subset.reduce((sum, d) => sum + d[field], 0) / subset.length;
result.push({
...data[i],
[field]: average
});
}
return result;
}
};
/**
* Color utilities
*/
export const ColorUtils = {
/**
* Generate color palette
*/
generatePalette: function(count, scheme = 'category10') {
const schemes = {
category10: ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf'],
pastel: ['#fbb4ae', '#b3cde3', '#ccebc5', '#decbe4', '#fed9a6', '#ffffcc', '#e5d8bd', '#fddaec', '#f2f2f2'],
dark: ['#1b9e77', '#d95f02', '#7570b3', '#e7298a', '#66a61e', '#e6ab02', '#a6761d', '#666666']
};
const colors = schemes[scheme] || schemes.category10;
const palette = [];
for (let i = 0; i < count; i++) {
palette.push(colors[i % colors.length]);
}
return palette;
},
/**
* Interpolate between two colors
*/
interpolateColor: function(color1, color2, factor) {
const c1 = this.hexToRgb(color1);
const c2 = this.hexToRgb(color2);
const r = Math.round(c1.r + (c2.r - c1.r) * factor);
const g = Math.round(c1.g + (c2.g - c1.g) * factor);
const b = Math.round(c1.b + (c2.b - c1.b) * factor);
return this.rgbToHex(r, g, b);
},
/**
* Convert hex to RGB
*/
hexToRgb: function(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
},
/**
* Convert RGB to hex
*/
rgbToHex: function(r, g, b) {
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
},
/**
* Darken a color
*/
darken: function(color, amount = 0.2) {
const rgb = this.hexToRgb(color);
if (!rgb) return color;
const r = Math.max(0, Math.round(rgb.r * (1 - amount)));
const g = Math.max(0, Math.round(rgb.g * (1 - amount)));
const b = Math.max(0, Math.round(rgb.b * (1 - amount)));
return this.rgbToHex(r, g, b);
},
/**
* Lighten a color
*/
lighten: function(color, amount = 0.2) {
const rgb = this.hexToRgb(color);
if (!rgb) return color;
const r = Math.min(255, Math.round(rgb.r + (255 - rgb.r) * amount));
const g = Math.min(255, Math.round(rgb.g + (255 - rgb.g) * amount));
const b = Math.min(255, Math.round(rgb.b + (255 - rgb.b) * amount));
return this.rgbToHex(r, g, b);
}
};
/**
* Animation utilities
*/
export const AnimationUtils = {
/**
* Easing functions
*/
easing: {
linear: t => t,
easeInQuad: t => t * t,
easeOutQuad: t => t * (2 - t),
easeInOutQuad: t => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
easeInCubic: t => t * t * t,
easeOutCubic: t => (--t) * t * t + 1,
easeInOutCubic: t => t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
},
/**
* Animate a value over time
*/
animate: function(from, to, duration, callback, easing = 'easeOutQuad') {
const start = Date.now();
const easingFn = this.easing[easing] || this.easing.linear;
function step() {
const elapsed = Date.now() - start;
const progress = Math.min(elapsed / duration, 1);
const easedProgress = easingFn(progress);
const currentValue = from + (to - from) * easedProgress;
callback(currentValue, progress);
if (progress < 1) {
requestAnimationFrame(step);
}
}
requestAnimationFrame(step);
},
/**
* Stagger animation delays
*/
stagger: function(items, delay = 100) {
return items.map((item, index) => ({
...item,
delay: index * delay
}));
}
};
/**
* Math utilities
*/
export const MathUtils = {
/**
* Calculate statistical measures
*/
stats: function(data, field = 'y') {
const values = data.map(d => d[field]).filter(v => !isNaN(v));
const sorted = values.slice().sort((a, b) => a - b);
const n = values.length;
if (n === 0) return null;
const sum = values.reduce((a, b) => a + b, 0);
const mean = sum / n;
const variance = values.reduce((acc, val) => acc + Math.pow(val - mean, 2), 0) / n;
return {
count: n,
min: Math.min(...values),
max: Math.max(...values),
sum: sum,
mean: mean,
median: n % 2 === 0 ? (sorted[n/2 - 1] + sorted[n/2]) / 2 : sorted[Math.floor(n/2)],
mode: this.calculateMode(values),
variance: variance,
standardDeviation: Math.sqrt(variance),
q1: this.percentile(sorted, 0.25),
q3: this.percentile(sorted, 0.75)
};
},
/**
* Calculate percentile
*/
percentile: function(sorted, p) {
const index = p * (sorted.length - 1);
const lower = Math.floor(index);
const upper = Math.ceil(index);
const weight = index % 1;
if (upper >= sorted.length) return sorted[sorted.length - 1];
return sorted[lower] * (1 - weight) + sorted[upper] * weight;
},
/**
* Calculate mode
*/
calculateMode: function(values) {
const frequency = {};
let maxFreq = 0;
let mode = null;
values.forEach(value => {
frequency[value] = (frequency[value] || 0) + 1;
if (frequency[value] > maxFreq) {
maxFreq = frequency[value];
mode = value;
}
});
return mode;
},
/**
* Linear interpolation
*/
lerp: function(a, b, t) {
return a + (b - a) * t;
},
/**
* Clamp value between min and max
*/
clamp: function(value, min, max) {
return Math.min(Math.max(value, min), max);
},
/**
* Map value from one range to another
*/
map: function(value, inMin, inMax, outMin, outMax) {
return (value - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
}
};
/**
* DOM utilities
*/
export const DOMUtils = {
/**
* Get element dimensions
*/
getDimensions: function(element) {
const rect = element.getBoundingClientRect();
return {
width: rect.width,
height: rect.height,
top: rect.top,
left: rect.left
};
},
/**
* Create responsive container
*/
makeResponsive: function(chart, container) {
const resizeObserver = new ResizeObserver(entries => {
for (let entry of entries) {
const { width, height } = entry.contentRect;
chart.updateOptions({ width, height });
chart.render();
}
});
resizeObserver.observe(container);
return resizeObserver;
},
/**
* Export chart as image
*/
exportAsImage: function(svgElement, filename = 'chart.png', scale = 2) {
const svgData = new XMLSerializer().serializeToString(svgElement);
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
const url = URL.createObjectURL(svgBlob);
img.onload = function() {
canvas.width = img.width * scale;
canvas.height = img.height * scale;
ctx.scale(scale, scale);
ctx.drawImage(img, 0, 0);
canvas.toBlob(blob => {
const link = document.createElement('a');
link.download = filename;
link.href = URL.createObjectURL(blob);
link.click();
URL.revokeObjectURL(url);
URL.revokeObjectURL(link.href);
});
};
img.src = url;
}
};