Replaces the Plotly-based graph generation in the SQL Explorer with direct SVG rendering within an HTML template, similar to the exercise progress sparklines. - Modifies `routes/sql_explorer.py` endpoints (`plot_query`, `plot_unsaved_query`) to fetch raw data instead of using pandas/Plotly. - Adds `utils.prepare_svg_plot_data` to process raw SQL results, determine plot type (scatter, line, bar, table), normalize data, and prepare it for SVG. - Creates `templates/partials/sql_explorer/svg_plot.html` to render the SVG plot with axes, ticks, labels, and basic tooltips. - Removes the `generate_plot` function's usage for SQL Explorer and the direct dependency on Plotly for this feature.
125 lines
6.0 KiB
HTML
125 lines
6.0 KiB
HTML
{# Basic SVG Plot Template for SQL Explorer #}
|
|
{% set unique_id = range(1000, 9999) | random %} {# Simple unique ID for elements #}
|
|
|
|
<div class="sql-plot-container p-4 border rounded bg-white shadow" id="sql-plot-{{ unique_id }}">
|
|
<h4 class="text-lg font-semibold text-gray-700 text-center mb-2">{{ title }}</h4>
|
|
|
|
{% if plot_type == 'table' %}
|
|
{# Fallback to rendering a table if plot type is not supported or data is unsuitable #}
|
|
<div class="overflow-x-auto max-h-96"> {# Limit height and allow scroll #}
|
|
<table class="min-w-full divide-y divide-gray-200 text-sm">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
{% for col in original_columns %}
|
|
<th scope="col" class="px-4 py-2 text-left font-medium text-gray-500 uppercase tracking-wider">
|
|
{{ col }}
|
|
</th>
|
|
{% endfor %}
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white divide-y divide-gray-200">
|
|
{% for row in original_results %}
|
|
<tr>
|
|
{% for col in original_columns %}
|
|
<td class="px-4 py-2 whitespace-nowrap">
|
|
{{ row[col] }}
|
|
</td>
|
|
{% endfor %}
|
|
</tr>
|
|
{% else %}
|
|
<tr>
|
|
<td colspan="{{ original_columns|length }}" class="px-4 py-2 text-center text-gray-500">No data
|
|
available.</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{% else %}
|
|
{# SVG Plot Area #}
|
|
<div class="relative" _="
|
|
on mouseover from .plot-point-{{ unique_id }}
|
|
get event.target @data-tooltip
|
|
if it
|
|
put it into #tooltip-{{ unique_id }}
|
|
remove .hidden from #tooltip-{{ unique_id }}
|
|
end
|
|
on mouseout from .plot-point-{{ unique_id }}
|
|
add .hidden to #tooltip-{{ unique_id }}
|
|
">
|
|
|
|
{# Tooltip Element #}
|
|
<div id="tooltip-{{ unique_id }}"
|
|
class="absolute top-0 left-0 hidden bg-gray-800 text-white text-xs p-1 rounded shadow-lg z-10 pointer-events-none">
|
|
Tooltip
|
|
</div>
|
|
|
|
<svg viewBox="0 0 {{ vb_width }} {{ vb_height }}" preserveAspectRatio="xMidYMid meet" class="w-full h-auto">
|
|
{# Draw Axes #}
|
|
<g class="axes" stroke="#6b7280" stroke-width="1">
|
|
{# Y Axis #}
|
|
<line x1="{{ margin.left }}" y1="{{ margin.top }}" x2="{{ margin.left }}"
|
|
y2="{{ vb_height - margin.bottom }}"></line>
|
|
{# X Axis #}
|
|
<line x1="{{ margin.left }}" y1="{{ vb_height - margin.bottom }}" x2="{{ vb_width - margin.right }}"
|
|
y2="{{ vb_height - margin.bottom }}"></line>
|
|
</g>
|
|
|
|
{# Draw Ticks and Grid Lines #}
|
|
<g class="ticks" font-size="10" fill="#6b7280" text-anchor="middle">
|
|
{# Y Ticks #}
|
|
{% for tick in y_ticks %}
|
|
<line x1="{{ margin.left - 5 }}" y1="{{ tick.position }}" x2="{{ vb_width - margin.right }}"
|
|
y2="{{ tick.position }}" stroke="#e5e7eb" stroke-width="0.5"></line> {# Grid line #}
|
|
<text x="{{ margin.left - 8 }}" y="{{ tick.position + 3 }}" text-anchor="end">{{ tick.label }}</text>
|
|
{% endfor %}
|
|
{# X Ticks #}
|
|
{% for tick in x_ticks %}
|
|
<line x1="{{ tick.position }}" y1="{{ margin.top }}" x2="{{ tick.position }}"
|
|
y2="{{ vb_height - margin.bottom + 5 }}" stroke="#e5e7eb" stroke-width="0.5"></line> {# Grid line #}
|
|
<text x="{{ tick.position }}" y="{{ vb_height - margin.bottom + 15 }}">{{ tick.label }}</text>
|
|
{% endfor %}
|
|
</g>
|
|
|
|
{# Draw Axis Labels #}
|
|
<g class="axis-labels" font-size="12" fill="#374151" text-anchor="middle">
|
|
{# Y Axis Label #}
|
|
<text
|
|
transform="translate({{ margin.left / 2 - 5 }}, {{ (vb_height - margin.bottom + margin.top) / 2 }}) rotate(-90)">{{
|
|
y_axis_label }}</text>
|
|
{# X Axis Label #}
|
|
<text x="{{ (vb_width - margin.right + margin.left) / 2 }}"
|
|
y="{{ vb_height - margin.bottom / 2 + 10 }}">{{ x_axis_label }}</text>
|
|
</g>
|
|
|
|
{# Plot Data Points/Bars #}
|
|
{% for plot in plots %}
|
|
<g class="plot-series-{{ loop.index }}" fill="{{ plot.color }}" stroke="{{ plot.color }}">
|
|
{% if plot_type == 'scatter' %}
|
|
{% for p in plot.points %}
|
|
<circle cx="{{ p.x }}" cy="{{ p.y }}" r="3" class="plot-point-{{ unique_id }}"
|
|
data-tooltip="{{ p.original | tojson | escape }}" />
|
|
{% endfor %}
|
|
{% elif plot_type == 'line' %}
|
|
<path
|
|
d="{% for p in plot.points %}{% if loop.first %}M{% else %}L{% endif %}{{ p.x }} {{ p.y }}{% endfor %}"
|
|
fill="none" stroke-width="1.5" />
|
|
{% for p in plot.points %}
|
|
<circle cx="{{ p.x }}" cy="{{ p.y }}" r="2.5" class="plot-point-{{ unique_id }}"
|
|
data-tooltip="{{ p.original | tojson | escape }}" />
|
|
{% endfor %}
|
|
{% elif plot_type == 'bar' %}
|
|
{% set bar_w = bar_width | default(10) %}
|
|
{% for p in plot.points %}
|
|
<rect x="{{ p.x - bar_w / 2 }}" y="{{ p.y }}" width="{{ bar_w }}"
|
|
height="{{ (vb_height - margin.bottom) - p.y }}" stroke-width="0.5"
|
|
class="plot-point-{{ unique_id }}" data-tooltip="{{ p.original | tojson | escape }}" />
|
|
{% endfor %}
|
|
{% endif %}
|
|
</g>
|
|
{% endfor %}
|
|
</svg>
|
|
</div>
|
|
{% endif %}
|
|
</div> |