mintwaterfall 0.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +223 -0
- package/CONTRIBUTING.md +199 -0
- package/README.md +363 -0
- package/dist/index.d.ts +149 -0
- package/dist/mintwaterfall.cjs.js +7978 -0
- package/dist/mintwaterfall.esm.js +7907 -0
- package/dist/mintwaterfall.min.js +7 -0
- package/dist/mintwaterfall.umd.js +7978 -0
- package/index.d.ts +149 -0
- package/package.json +126 -0
- package/src/enterprise/enterprise-core.js +0 -0
- package/src/enterprise/enterprise-feature-template.js +0 -0
- package/src/enterprise/feature-registry.js +0 -0
- package/src/enterprise/features/breakdown.js +0 -0
- package/src/features/breakdown.js +0 -0
- package/src/features/conditional-formatting.js +0 -0
- package/src/index.js +111 -0
- package/src/mintwaterfall-accessibility.ts +680 -0
- package/src/mintwaterfall-advanced-data.ts +1034 -0
- package/src/mintwaterfall-advanced-interactions.ts +649 -0
- package/src/mintwaterfall-advanced-performance.ts +582 -0
- package/src/mintwaterfall-animations.ts +595 -0
- package/src/mintwaterfall-brush.ts +471 -0
- package/src/mintwaterfall-chart-core.ts +296 -0
- package/src/mintwaterfall-chart.ts +1915 -0
- package/src/mintwaterfall-data.ts +1100 -0
- package/src/mintwaterfall-export.ts +475 -0
- package/src/mintwaterfall-hierarchical-layouts.ts +724 -0
- package/src/mintwaterfall-layouts.ts +647 -0
- package/src/mintwaterfall-performance.ts +573 -0
- package/src/mintwaterfall-scales.ts +437 -0
- package/src/mintwaterfall-shapes.ts +385 -0
- package/src/mintwaterfall-statistics.ts +821 -0
- package/src/mintwaterfall-themes.ts +391 -0
- package/src/mintwaterfall-tooltip.ts +450 -0
- package/src/mintwaterfall-zoom.ts +399 -0
- package/src/types/js-modules.d.ts +25 -0
- package/src/utils/compatibility-layer.js +0 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
// MintWaterfall Chart - TypeScript Core Module (Gradual Migration)
|
|
2
|
+
// This is the first module converted as part of gradual migration strategy
|
|
3
|
+
|
|
4
|
+
import * as d3 from 'd3';
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// TYPE DEFINITIONS
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
export interface StackData {
|
|
11
|
+
value: number;
|
|
12
|
+
color: string;
|
|
13
|
+
label?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ChartData {
|
|
17
|
+
label: string;
|
|
18
|
+
stacks: StackData[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ProcessedData extends ChartData {
|
|
22
|
+
barTotal: number;
|
|
23
|
+
cumulativeTotal: number;
|
|
24
|
+
prevCumulativeTotal?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface MarginConfig {
|
|
28
|
+
top: number;
|
|
29
|
+
right: number;
|
|
30
|
+
bottom: number;
|
|
31
|
+
left: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface WaterfallChartConfig {
|
|
35
|
+
width: number;
|
|
36
|
+
height: number;
|
|
37
|
+
margin: MarginConfig;
|
|
38
|
+
showTotal: boolean;
|
|
39
|
+
totalLabel: string;
|
|
40
|
+
totalColor: string;
|
|
41
|
+
stacked: boolean;
|
|
42
|
+
barPadding: number;
|
|
43
|
+
duration: number;
|
|
44
|
+
formatNumber: (n: number) => string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// MAIN CHART FACTORY FUNCTION
|
|
49
|
+
// ============================================================================
|
|
50
|
+
|
|
51
|
+
export function waterfallChart() {
|
|
52
|
+
// Configuration with defaults
|
|
53
|
+
let config: WaterfallChartConfig = {
|
|
54
|
+
width: 800,
|
|
55
|
+
height: 400,
|
|
56
|
+
margin: { top: 60, right: 80, bottom: 60, left: 80 },
|
|
57
|
+
showTotal: false,
|
|
58
|
+
totalLabel: "Total",
|
|
59
|
+
totalColor: "#95A5A6",
|
|
60
|
+
stacked: true,
|
|
61
|
+
barPadding: 0.1,
|
|
62
|
+
duration: 750,
|
|
63
|
+
formatNumber: d3.format(".0f")
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Cache for performance
|
|
67
|
+
let lastDataHash: string | null = null;
|
|
68
|
+
let cachedProcessedData: ProcessedData[] | null = null;
|
|
69
|
+
|
|
70
|
+
function chart(selection: d3.Selection<any, any, any, any>): void {
|
|
71
|
+
selection.each(function(data: ChartData[]) {
|
|
72
|
+
// Basic data validation
|
|
73
|
+
if (!data || !Array.isArray(data) || data.length === 0) {
|
|
74
|
+
console.warn("MintWaterfall: Invalid or empty data provided");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Process data
|
|
79
|
+
const processedData = prepareData(data);
|
|
80
|
+
|
|
81
|
+
// Render chart
|
|
82
|
+
renderChart(d3.select(this), processedData);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ========================================================================
|
|
87
|
+
// CORE PROCESSING FUNCTIONS
|
|
88
|
+
// ========================================================================
|
|
89
|
+
|
|
90
|
+
function prepareData(data: ChartData[]): ProcessedData[] {
|
|
91
|
+
// Quick hash for caching
|
|
92
|
+
const dataHash = JSON.stringify(data).slice(0, 100) + config.showTotal.toString();
|
|
93
|
+
|
|
94
|
+
if (dataHash === lastDataHash && cachedProcessedData) {
|
|
95
|
+
return cachedProcessedData;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let cumulativeTotal = 0;
|
|
99
|
+
const processedData: ProcessedData[] = data.map((item, i) => {
|
|
100
|
+
const barTotal = item.stacks.reduce((sum, stack) => sum + stack.value, 0);
|
|
101
|
+
const prevCumulativeTotal = cumulativeTotal;
|
|
102
|
+
cumulativeTotal += barTotal;
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
...item,
|
|
106
|
+
barTotal,
|
|
107
|
+
cumulativeTotal,
|
|
108
|
+
prevCumulativeTotal: i === 0 ? 0 : prevCumulativeTotal
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Add total bar if enabled
|
|
113
|
+
if (config.showTotal && processedData.length > 0) {
|
|
114
|
+
processedData.push({
|
|
115
|
+
label: config.totalLabel,
|
|
116
|
+
stacks: [{ value: cumulativeTotal, color: config.totalColor }],
|
|
117
|
+
barTotal: cumulativeTotal,
|
|
118
|
+
cumulativeTotal,
|
|
119
|
+
prevCumulativeTotal: 0
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Cache results
|
|
124
|
+
lastDataHash = dataHash;
|
|
125
|
+
cachedProcessedData = processedData;
|
|
126
|
+
|
|
127
|
+
return processedData;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function renderChart(svg: d3.Selection<any, any, any, any>, data: ProcessedData[]): void {
|
|
131
|
+
// Clear previous content
|
|
132
|
+
svg.selectAll("*").remove();
|
|
133
|
+
|
|
134
|
+
// Calculate dimensions
|
|
135
|
+
const { width, height, margin } = config;
|
|
136
|
+
const innerWidth = width - margin.left - margin.right;
|
|
137
|
+
const innerHeight = height - margin.top - margin.bottom;
|
|
138
|
+
|
|
139
|
+
// Create main container
|
|
140
|
+
const g = svg.append("g")
|
|
141
|
+
.attr("transform", `translate(${margin.left},${margin.top})`);
|
|
142
|
+
|
|
143
|
+
// Set up scales
|
|
144
|
+
const xScale = d3.scaleBand()
|
|
145
|
+
.domain(data.map(d => d.label))
|
|
146
|
+
.range([0, innerWidth])
|
|
147
|
+
.padding(config.barPadding);
|
|
148
|
+
|
|
149
|
+
const yExtent = d3.extent(data, d => d.cumulativeTotal) as [number, number];
|
|
150
|
+
const yScale = d3.scaleLinear()
|
|
151
|
+
.domain([Math.min(0, yExtent[0]), yExtent[1]])
|
|
152
|
+
.range([innerHeight, 0])
|
|
153
|
+
.nice();
|
|
154
|
+
|
|
155
|
+
// Create axes
|
|
156
|
+
const xAxis = d3.axisBottom(xScale);
|
|
157
|
+
const yAxis = d3.axisLeft(yScale).tickFormat((d: d3.NumberValue) => config.formatNumber(d.valueOf()));
|
|
158
|
+
|
|
159
|
+
g.append("g")
|
|
160
|
+
.attr("class", "x-axis")
|
|
161
|
+
.attr("transform", `translate(0,${innerHeight})`)
|
|
162
|
+
.call(xAxis);
|
|
163
|
+
|
|
164
|
+
g.append("g")
|
|
165
|
+
.attr("class", "y-axis")
|
|
166
|
+
.call(yAxis);
|
|
167
|
+
|
|
168
|
+
// Draw bars
|
|
169
|
+
const bars = g.selectAll(".bar")
|
|
170
|
+
.data(data)
|
|
171
|
+
.enter().append("g")
|
|
172
|
+
.attr("class", "bar")
|
|
173
|
+
.attr("transform", d => `translate(${xScale(d.label)},0)`);
|
|
174
|
+
|
|
175
|
+
if (config.stacked) {
|
|
176
|
+
drawStackedBars(bars, xScale, yScale);
|
|
177
|
+
} else {
|
|
178
|
+
drawWaterfallBars(bars, xScale, yScale);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Add value labels
|
|
182
|
+
addValueLabels(bars, xScale, yScale);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function drawStackedBars(bars: d3.Selection<SVGGElement, ProcessedData, any, any>,
|
|
186
|
+
xScale: d3.ScaleBand<string>,
|
|
187
|
+
yScale: d3.ScaleLinear<number, number>): void {
|
|
188
|
+
bars.each(function(d) {
|
|
189
|
+
const bar = d3.select(this);
|
|
190
|
+
let yPos = d.prevCumulativeTotal || 0;
|
|
191
|
+
|
|
192
|
+
d.stacks.forEach(stack => {
|
|
193
|
+
const rectHeight = Math.abs(yScale(yPos) - yScale(yPos + stack.value));
|
|
194
|
+
const rectY = yScale(Math.max(yPos, yPos + stack.value));
|
|
195
|
+
|
|
196
|
+
bar.append("rect")
|
|
197
|
+
.attr("width", xScale.bandwidth())
|
|
198
|
+
.attr("height", rectHeight)
|
|
199
|
+
.attr("y", rectY)
|
|
200
|
+
.attr("fill", stack.color)
|
|
201
|
+
.attr("stroke", "#fff")
|
|
202
|
+
.attr("stroke-width", 1);
|
|
203
|
+
|
|
204
|
+
yPos += stack.value;
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function drawWaterfallBars(bars: d3.Selection<SVGGElement, ProcessedData, any, any>,
|
|
210
|
+
xScale: d3.ScaleBand<string>,
|
|
211
|
+
yScale: d3.ScaleLinear<number, number>): void {
|
|
212
|
+
bars.each(function(d) {
|
|
213
|
+
const bar = d3.select(this);
|
|
214
|
+
const startY = d.prevCumulativeTotal || 0;
|
|
215
|
+
const endY = d.cumulativeTotal;
|
|
216
|
+
const rectHeight = Math.abs(yScale(startY) - yScale(endY));
|
|
217
|
+
const rectY = yScale(Math.max(startY, endY));
|
|
218
|
+
|
|
219
|
+
// Use first stack color or generate based on positive/negative
|
|
220
|
+
const color = d.stacks[0]?.color || (d.barTotal >= 0 ? "#2ecc71" : "#e74c3c");
|
|
221
|
+
|
|
222
|
+
bar.append("rect")
|
|
223
|
+
.attr("width", xScale.bandwidth())
|
|
224
|
+
.attr("height", rectHeight)
|
|
225
|
+
.attr("y", rectY)
|
|
226
|
+
.attr("fill", color)
|
|
227
|
+
.attr("stroke", "#fff")
|
|
228
|
+
.attr("stroke-width", 1);
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function addValueLabels(bars: d3.Selection<SVGGElement, ProcessedData, any, any>,
|
|
233
|
+
xScale: d3.ScaleBand<string>,
|
|
234
|
+
yScale: d3.ScaleLinear<number, number>): void {
|
|
235
|
+
bars.append("text")
|
|
236
|
+
.attr("x", xScale.bandwidth() / 2)
|
|
237
|
+
.attr("y", d => yScale(d.cumulativeTotal) - 5)
|
|
238
|
+
.attr("text-anchor", "middle")
|
|
239
|
+
.style("font-size", "12px")
|
|
240
|
+
.style("font-family", "Arial, sans-serif")
|
|
241
|
+
.text(d => config.formatNumber(d.cumulativeTotal));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ========================================================================
|
|
245
|
+
// GETTER/SETTER API (TypeScript style with proper overloads)
|
|
246
|
+
// ========================================================================
|
|
247
|
+
|
|
248
|
+
function createAccessor<T>(key: keyof WaterfallChartConfig) {
|
|
249
|
+
return function(value?: T): any {
|
|
250
|
+
if (arguments.length === 0) {
|
|
251
|
+
return config[key];
|
|
252
|
+
}
|
|
253
|
+
(config as any)[key] = value;
|
|
254
|
+
return chart;
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Public API using accessor pattern
|
|
259
|
+
chart.width = createAccessor<number>('width');
|
|
260
|
+
chart.height = createAccessor<number>('height');
|
|
261
|
+
chart.margin = createAccessor<MarginConfig>('margin');
|
|
262
|
+
chart.showTotal = createAccessor<boolean>('showTotal');
|
|
263
|
+
chart.totalLabel = createAccessor<string>('totalLabel');
|
|
264
|
+
chart.totalColor = createAccessor<string>('totalColor');
|
|
265
|
+
chart.stacked = createAccessor<boolean>('stacked');
|
|
266
|
+
chart.barPadding = createAccessor<number>('barPadding');
|
|
267
|
+
chart.duration = createAccessor<number>('duration');
|
|
268
|
+
chart.formatNumber = createAccessor<(n: number) => string>('formatNumber');
|
|
269
|
+
|
|
270
|
+
// Utility methods
|
|
271
|
+
chart.config = function(): WaterfallChartConfig {
|
|
272
|
+
return { ...config };
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
chart.reset = function() {
|
|
276
|
+
lastDataHash = null;
|
|
277
|
+
cachedProcessedData = null;
|
|
278
|
+
return chart;
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
return chart;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ============================================================================
|
|
285
|
+
// INTEGRATION WITH D3 (Temporary for gradual migration)
|
|
286
|
+
// ============================================================================
|
|
287
|
+
|
|
288
|
+
// Add to d3 namespace for compatibility
|
|
289
|
+
declare module 'd3' {
|
|
290
|
+
interface D3 {
|
|
291
|
+
waterfallChart(): any;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Extend d3 object
|
|
296
|
+
(d3 as any).waterfallChart = waterfallChart;
|