galbendavids commited on
Commit
79b2b9c
·
1 Parent(s): c503371

שיפור visualization generator - הוספת תמיכה אמיתית בגרפיקות עם Chart.js (bar, line, scatter, histogram)

Browse files
Files changed (3) hide show
  1. app/sql_service.py +49 -2
  2. app/static/app.js +220 -11
  3. app/static/index.html +1 -0
app/sql_service.py CHANGED
@@ -347,16 +347,42 @@ class SQLFeedbackService:
347
  # Determine visualization type based on result structure
348
  result = qr.result
349
 
350
- # If result has 2 columns, might be a bar chart
351
  if len(result.columns) == 2:
352
  col1, col2 = result.columns
353
  # If first column is categorical and second is numeric
354
  if result[col2].dtype in ['int64', 'float64']:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
355
  visualizations.append({
356
- "type": "bar",
357
  "title": f"תוצאה של שאילתה {i}",
358
  "x": col1,
359
  "y": col2,
 
 
360
  "data": result.to_dict('records')
361
  })
362
 
@@ -368,8 +394,29 @@ class SQLFeedbackService:
368
  "type": "histogram",
369
  "title": f"תוצאה של שאילתה {i}",
370
  "x": col,
 
371
  "data": result[col].tolist()
372
  })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
373
 
374
  return visualizations if visualizations else None
375
 
 
347
  # Determine visualization type based on result structure
348
  result = qr.result
349
 
350
+ # If result has 2 columns, might be a bar chart or line chart
351
  if len(result.columns) == 2:
352
  col1, col2 = result.columns
353
  # If first column is categorical and second is numeric
354
  if result[col2].dtype in ['int64', 'float64']:
355
+ # Check if it's a time series (col1 looks like date/time)
356
+ if 'date' in col1.lower() or 'time' in col1.lower() or 'תאריך' in col1.lower():
357
+ visualizations.append({
358
+ "type": "line",
359
+ "title": f"תוצאה של שאילתה {i}",
360
+ "x": col1,
361
+ "y": col2,
362
+ "x_label": col1,
363
+ "y_label": col2,
364
+ "data": result.to_dict('records')
365
+ })
366
+ else:
367
+ # Bar chart for categorical data
368
+ visualizations.append({
369
+ "type": "bar",
370
+ "title": f"תוצאה של שאילתה {i}",
371
+ "x": col1,
372
+ "y": col2,
373
+ "x_label": col1,
374
+ "y_label": col2,
375
+ "data": result.to_dict('records')
376
+ })
377
+ # If both are numeric, might be a scatter plot
378
+ elif result[col1].dtype in ['int64', 'float64'] and result[col2].dtype in ['int64', 'float64']:
379
  visualizations.append({
380
+ "type": "scatter",
381
  "title": f"תוצאה של שאילתה {i}",
382
  "x": col1,
383
  "y": col2,
384
+ "x_label": col1,
385
+ "y_label": col2,
386
  "data": result.to_dict('records')
387
  })
388
 
 
394
  "type": "histogram",
395
  "title": f"תוצאה של שאילתה {i}",
396
  "x": col,
397
+ "x_label": col,
398
  "data": result[col].tolist()
399
  })
400
+
401
+ # If result has 3+ columns, try to find the best visualization
402
+ elif len(result.columns) >= 3:
403
+ # Look for numeric columns
404
+ numeric_cols = [c for c in result.columns if result[c].dtype in ['int64', 'float64']]
405
+ categorical_cols = [c for c in result.columns if result[c].dtype == 'object']
406
+
407
+ # If we have one categorical and one numeric, use bar chart
408
+ if len(categorical_cols) >= 1 and len(numeric_cols) >= 1:
409
+ cat_col = categorical_cols[0]
410
+ num_col = numeric_cols[0]
411
+ visualizations.append({
412
+ "type": "bar",
413
+ "title": f"תוצאה של שאילתה {i}",
414
+ "x": cat_col,
415
+ "y": num_col,
416
+ "x_label": cat_col,
417
+ "y_label": num_col,
418
+ "data": result.to_dict('records')
419
+ })
420
 
421
  return visualizations if visualizations else None
422
 
app/static/app.js CHANGED
@@ -201,8 +201,9 @@ function showVisualizations(visualizations) {
201
  document.getElementById('last-response').appendChild(vizContainer);
202
  }
203
 
204
- vizContainer.style.display = 'block';
205
  vizContainer.innerHTML = '<h4 style="color: #1976d2; margin-bottom: 12px;">📊 גרפיקות:</h4>';
 
206
 
207
  visualizations.forEach((viz, idx) => {
208
  const vizDiv = document.createElement('div');
@@ -211,22 +212,230 @@ function showVisualizations(visualizations) {
211
  vizDiv.style.background = '#f8f9fa';
212
  vizDiv.style.borderRadius = '8px';
213
 
214
- vizDiv.innerHTML = `<h5 style="margin-top: 0; color: #1976d2;">${viz.title}</h5>`;
215
- vizDiv.innerHTML += `<div id="viz-${idx}" style="height: 300px;"></div>`;
 
 
 
 
216
 
217
  vizContainer.appendChild(vizDiv);
218
 
219
- // Use Chart.js or similar for visualization
220
- // For now, just show a placeholder
221
- document.getElementById(`viz-${idx}`).innerHTML = `
222
- <div style="text-align: center; padding: 40px; color: #666;">
223
- 📊 גרפיקה מסוג ${viz.type}<br>
224
- <small>תמיכה בגרפיקות תתווסף בקרוב</small>
225
- </div>
226
- `;
 
 
 
227
  });
228
  }
229
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
  function formatResponse(text) {
231
  // Format markdown-like text (bold, lists, etc.)
232
  let formatted = escapeHtml(text);
 
201
  document.getElementById('last-response').appendChild(vizContainer);
202
  }
203
 
204
+ // Clear previous visualizations
205
  vizContainer.innerHTML = '<h4 style="color: #1976d2; margin-bottom: 12px;">📊 גרפיקות:</h4>';
206
+ vizContainer.style.display = 'block';
207
 
208
  visualizations.forEach((viz, idx) => {
209
  const vizDiv = document.createElement('div');
 
212
  vizDiv.style.background = '#f8f9fa';
213
  vizDiv.style.borderRadius = '8px';
214
 
215
+ vizDiv.innerHTML = `<h5 style="margin-top: 0; color: #1976d2;">${escapeHtml(viz.title)}</h5>`;
216
+ const canvasDiv = document.createElement('div');
217
+ canvasDiv.style.position = 'relative';
218
+ canvasDiv.style.height = '300px';
219
+ canvasDiv.innerHTML = `<canvas id="chart-${idx}"></canvas>`;
220
+ vizDiv.appendChild(canvasDiv);
221
 
222
  vizContainer.appendChild(vizDiv);
223
 
224
+ // Create chart using Chart.js
225
+ setTimeout(() => {
226
+ const canvas = document.getElementById(`chart-${idx}`);
227
+ if (!canvas) return;
228
+
229
+ const ctx = canvas.getContext('2d');
230
+ const config = getChartConfig(viz, idx);
231
+ if (config) {
232
+ new Chart(ctx, config);
233
+ }
234
+ }, 100);
235
  });
236
  }
237
 
238
+ function getChartConfig(viz, idx) {
239
+ if (!viz.data || viz.data.length === 0) return null;
240
+
241
+ const xLabel = viz.x_label || viz.x || 'X';
242
+ const yLabel = viz.y_label || viz.y || 'Y';
243
+
244
+ switch (viz.type) {
245
+ case 'bar':
246
+ return {
247
+ type: 'bar',
248
+ data: {
249
+ labels: viz.data.map(d => String(d[viz.x])),
250
+ datasets: [{
251
+ label: yLabel,
252
+ data: viz.data.map(d => d[viz.y]),
253
+ backgroundColor: 'rgba(25, 118, 210, 0.6)',
254
+ borderColor: 'rgba(25, 118, 210, 1)',
255
+ borderWidth: 1
256
+ }]
257
+ },
258
+ options: {
259
+ responsive: true,
260
+ maintainAspectRatio: false,
261
+ plugins: {
262
+ legend: {
263
+ display: true,
264
+ position: 'top'
265
+ },
266
+ title: {
267
+ display: false
268
+ }
269
+ },
270
+ scales: {
271
+ y: {
272
+ beginAtZero: true,
273
+ title: {
274
+ display: true,
275
+ text: yLabel
276
+ }
277
+ },
278
+ x: {
279
+ title: {
280
+ display: true,
281
+ text: xLabel
282
+ }
283
+ }
284
+ }
285
+ }
286
+ };
287
+
288
+ case 'line':
289
+ return {
290
+ type: 'line',
291
+ data: {
292
+ labels: viz.data.map(d => String(d[viz.x])),
293
+ datasets: [{
294
+ label: yLabel,
295
+ data: viz.data.map(d => d[viz.y]),
296
+ borderColor: 'rgba(25, 118, 210, 1)',
297
+ backgroundColor: 'rgba(25, 118, 210, 0.1)',
298
+ borderWidth: 2,
299
+ fill: true,
300
+ tension: 0.4
301
+ }]
302
+ },
303
+ options: {
304
+ responsive: true,
305
+ maintainAspectRatio: false,
306
+ plugins: {
307
+ legend: {
308
+ display: true,
309
+ position: 'top'
310
+ }
311
+ },
312
+ scales: {
313
+ y: {
314
+ beginAtZero: true,
315
+ title: {
316
+ display: true,
317
+ text: yLabel
318
+ }
319
+ },
320
+ x: {
321
+ title: {
322
+ display: true,
323
+ text: xLabel
324
+ }
325
+ }
326
+ }
327
+ }
328
+ };
329
+
330
+ case 'scatter':
331
+ return {
332
+ type: 'scatter',
333
+ data: {
334
+ datasets: [{
335
+ label: `${xLabel} vs ${yLabel}`,
336
+ data: viz.data.map(d => ({
337
+ x: d[viz.x],
338
+ y: d[viz.y]
339
+ })),
340
+ backgroundColor: 'rgba(25, 118, 210, 0.6)',
341
+ borderColor: 'rgba(25, 118, 210, 1)',
342
+ borderWidth: 1
343
+ }]
344
+ },
345
+ options: {
346
+ responsive: true,
347
+ maintainAspectRatio: false,
348
+ plugins: {
349
+ legend: {
350
+ display: true,
351
+ position: 'top'
352
+ }
353
+ },
354
+ scales: {
355
+ y: {
356
+ beginAtZero: true,
357
+ title: {
358
+ display: true,
359
+ text: yLabel
360
+ }
361
+ },
362
+ x: {
363
+ beginAtZero: true,
364
+ title: {
365
+ display: true,
366
+ text: xLabel
367
+ }
368
+ }
369
+ }
370
+ }
371
+ };
372
+
373
+ case 'histogram':
374
+ // For histogram, we need to create bins
375
+ const values = viz.data.filter(v => v != null && !isNaN(v));
376
+ if (values.length === 0) return null;
377
+
378
+ const min = Math.min(...values);
379
+ const max = Math.max(...values);
380
+ const binCount = Math.min(20, Math.ceil(Math.sqrt(values.length)));
381
+ const binSize = (max - min) / binCount;
382
+
383
+ const bins = Array(binCount).fill(0);
384
+ const binLabels = [];
385
+
386
+ for (let i = 0; i < binCount; i++) {
387
+ binLabels.push((min + i * binSize).toFixed(1));
388
+ }
389
+
390
+ values.forEach(v => {
391
+ const binIndex = Math.min(Math.floor((v - min) / binSize), binCount - 1);
392
+ bins[binIndex]++;
393
+ });
394
+
395
+ return {
396
+ type: 'bar',
397
+ data: {
398
+ labels: binLabels,
399
+ datasets: [{
400
+ label: xLabel,
401
+ data: bins,
402
+ backgroundColor: 'rgba(25, 118, 210, 0.6)',
403
+ borderColor: 'rgba(25, 118, 210, 1)',
404
+ borderWidth: 1
405
+ }]
406
+ },
407
+ options: {
408
+ responsive: true,
409
+ maintainAspectRatio: false,
410
+ plugins: {
411
+ legend: {
412
+ display: true,
413
+ position: 'top'
414
+ }
415
+ },
416
+ scales: {
417
+ y: {
418
+ beginAtZero: true,
419
+ title: {
420
+ display: true,
421
+ text: 'תדירות'
422
+ }
423
+ },
424
+ x: {
425
+ title: {
426
+ display: true,
427
+ text: xLabel
428
+ }
429
+ }
430
+ }
431
+ }
432
+ };
433
+
434
+ default:
435
+ return null;
436
+ }
437
+ }
438
+
439
  function formatResponse(text) {
440
  // Format markdown-like text (bold, lists, etc.)
441
  let formatted = escapeHtml(text);
app/static/index.html CHANGED
@@ -4,6 +4,7 @@
4
  <meta charset="utf-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
  <title>Feedback RAG — Frontend</title>
 
7
  <style>
8
  * { box-sizing: border-box; }
9
  body {
 
4
  <meta charset="utf-8" />
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
  <title>Feedback RAG — Frontend</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
8
  <style>
9
  * { box-sizing: border-box; }
10
  body {