Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
242 changes: 4 additions & 238 deletions google/colab/_quickchart_hint_button.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,255 +13,20 @@
# limitations under the License.
"""Formatter used to display a data table conversion button next to dataframes."""

import logging
import textwrap
import uuid as _uuid
import weakref as _weakref

from google.colab import _generate_with_variable
from google.colab import _interactive_table_hint_button
from google.colab import _quickchart
from google.colab import output
import IPython as _IPython


_output_callbacks = {}
_MAX_CHART_INSTANCES = 4
_ENABLE_GENERATE = False
_ENABLE_SUGGEST_PLOTS = False
_QUICKCHART_BUTTON_MIN_ROW_COUNT = 2 # Min # rows to enable quickchart button.

_ICON_SVG = textwrap.dedent("""
<svg xmlns="http://www.w3.org/2000/svg" height="24px"viewBox="0 0 24 24"
width="24px">
<g>
<path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17H7v-7h2v7zm4 0h-2V7h2v10zm4 0h-2v-4h2v4z"/>
</g>
</svg>""")

_HINT_BUTTON_CSS = textwrap.dedent("""
<style>
.colab-df-quickchart {
--bg-color: #E8F0FE;
--fill-color: #1967D2;
--hover-bg-color: #E2EBFA;
--hover-fill-color: #174EA6;
--disabled-fill-color: #AAA;
--disabled-bg-color: #DDD;
}
[theme=dark] .colab-df-quickchart {
--bg-color: #3B4455;
--fill-color: #D2E3FC;
--hover-bg-color: #434B5C;
--hover-fill-color: #FFFFFF;
--disabled-bg-color: #3B4455;
--disabled-fill-color: #666;
}
.colab-df-quickchart {
background-color: var(--bg-color);
border: none;
border-radius: 50%;
cursor: pointer;
display: none;
fill: var(--fill-color);
height: 32px;
padding: 0;
width: 32px;
}
.colab-df-quickchart:hover {
background-color: var(--hover-bg-color);
box-shadow: 0 1px 2px rgba(60, 64, 67, 0.3), 0 1px 3px 1px rgba(60, 64, 67, 0.15);
fill: var(--button-hover-fill-color);
}
.colab-df-quickchart-complete:disabled,
.colab-df-quickchart-complete:disabled:hover {
background-color: var(--disabled-bg-color);
fill: var(--disabled-fill-color);
box-shadow: none;
}
.colab-df-spinner {
border: 2px solid var(--fill-color);
border-color: transparent;
border-bottom-color: var(--fill-color);
animation:
spin 1s steps(1) infinite;
}
@keyframes spin {
0% {
border-color: transparent;
border-bottom-color: var(--fill-color);
border-left-color: var(--fill-color);
}
20% {
border-color: transparent;
border-left-color: var(--fill-color);
border-top-color: var(--fill-color);
}
30% {
border-color: transparent;
border-left-color: var(--fill-color);
border-top-color: var(--fill-color);
border-right-color: var(--fill-color);
}
40% {
border-color: transparent;
border-right-color: var(--fill-color);
border-top-color: var(--fill-color);
}
60% {
border-color: transparent;
border-right-color: var(--fill-color);
}
80% {
border-color: transparent;
border-right-color: var(--fill-color);
border-bottom-color: var(--fill-color);
}
90% {
border-color: transparent;
border-bottom-color: var(--fill-color);
}
}
</style>
""")


class DataframeCache(object):
"""Cache of dataframes that may be requested for output visualization.
Purposely uses weakref to allow memory to be freed for dataframes which are no
longer referenced elsewhere, rather than accumulating all dataframes seen so
far.
"""

def __init__(self):
"""Constructor."""
# Cache of non-interactive dfs that still have live references and could be
# printed as interactive dfs.
self._noninteractive_df_refs = _weakref.WeakValueDictionary()
# Single entry cache that stores a shallow copy of the last printed df.
self._last_noninteractive_df = {}

def __getitem__(self, key):
"""Gets a dataframe by the given key if it still exists."""
if key in self._last_noninteractive_df:
return self._last_noninteractive_df.pop(key)
elif key in self._noninteractive_df_refs:
return self._noninteractive_df_refs.pop(key)
raise KeyError('Dataframe key "%s" was not found' % key)

def __setitem__(self, key, df):
"""Adds the given dataframe to the cache."""
self._noninteractive_df_refs[key] = df

# Ensure our last value cache only contains one item.
self._last_noninteractive_df.clear()
self._last_noninteractive_df[key] = df

def keys(self):
return list(
set(self._noninteractive_df_refs.keys()).union(
set(self._last_noninteractive_df.keys())
)
)


_df_cache = DataframeCache()
_chart_cache = {}


def _suggest_charts(df_key):
"""Generates and displays a set of charts from the specified dataframe.
Args:
df_key: (str) The dataframe key (element id).
"""
try:
df = _df_cache[df_key]
except KeyError:
print(
'WARNING: Runtime no longer has a reference to this dataframe, please'
' re-run this cell and try again.'
)
return

for chart_section in _quickchart.find_charts(
df, max_chart_instances=_MAX_CHART_INSTANCES
):
for chart in chart_section.charts:
_chart_cache[chart.chart_id] = chart
chart_section.display()


def _get_code_for_chart(chart_key):
if chart_key in _chart_cache:
chart_code = _chart_cache[chart_key].get_code()
return _IPython.display.JSON(dict(code=chart_code))
else:
logging.error('Did not find quickchart key %s in chart cache', chart_key)
return f'Could not find code for chart {chart_key}'


output.register_callback('getCodeForChart', _get_code_for_chart)
output.register_callback('suggestCharts', _suggest_charts)


def register_df_and_get_html(df):
"""Registers a dataframe and returns HTML with a quickchart button.
Args:
df: (DataFrame) The dataframe to register.
Returns:
(str) The HTML for the quickchart button.
"""
df_key = f'df-{str(_uuid.uuid4())}'
_df_cache[df_key] = df
html = textwrap.dedent(f"""
<div id="{df_key}">
<button class="colab-df-quickchart" onclick="quickchart('{df_key}')"
title="Suggest charts"
style="display:none;">
{_ICON_SVG}
</button>
{_HINT_BUTTON_CSS}
<script>
async function quickchart(key) {{
const quickchartButtonEl =
document.querySelector('#' + key + ' button');
quickchartButtonEl.disabled = true; // To prevent multiple clicks.
quickchartButtonEl.classList.add('colab-df-spinner');
try {{
const charts = await google.colab.kernel.invokeFunction(
'suggestCharts', [key], {{}});
}} catch (error) {{
console.error('Error during call to suggestCharts:', error);
}}
quickchartButtonEl.classList.remove('colab-df-spinner');
quickchartButtonEl.classList.add('colab-df-quickchart-complete');
}}
(() => {{
let quickchartButtonEl =
document.querySelector('#{df_key} button');
quickchartButtonEl.style.display =
google.colab.kernel.accessAllowed ? 'block' : 'none';
}})();
</script>
</div>""")
return html


def _df_formatter_with_hint_buttons(df):
"""Alternate df formatter with buttons for interactive and quickchart."""
"""Alternate df formatter with buttons for interactive."""

buttons = []
if _ENABLE_SUGGEST_PLOTS and len(df) >= _QUICKCHART_BUTTON_MIN_ROW_COUNT:
buttons.append(register_df_and_get_html(df))
if _ENABLE_GENERATE:
buttons.append(_generate_with_variable.get_html(df))
# pylint: disable=protected-access
Expand All @@ -275,7 +40,8 @@ def _df_formatter_with_hint_buttons(df):


def _enable_df_interactive_hint_formatter():
"""Formatter that surfaces interactive tables and quickchart to user."""
"""Formatter that surfaces interactive tables to user."""

shell = _IPython.get_ipython()
if not shell:
return
Expand Down
14 changes: 1 addition & 13 deletions google/colab/data_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import traceback as _traceback

from google.colab import _interactive_table_helper
from google.colab import _quickchart_hint_button
from google.colab import autoviz as _autoviz
from google.colab import widgets as _widgets
import IPython as _IPython
Expand Down Expand Up @@ -137,6 +136,7 @@ def __init__(
shrink down to the minimum of this value and the width needed for the
content.
"""

def _default(value, default):
return default if value is None else value

Expand Down Expand Up @@ -231,9 +231,6 @@ def _gen_js(self, dataframe):
'width': '1px',
'className': 'index_column',
}] * self._dataframe.index.nlevels
quickchart_button_html = _quickchart_hint_button.register_df_and_get_html(
dataframe
)
return """
import "{gviz_url}";
Expand All @@ -246,14 +243,6 @@ def _gen_js(self, dataframe):
suppressOutputScrolling: {suppress_output_scrolling},
minimumWidth: {min_width},
}});
function appendQuickchartButton(parentElement) {{
let quickchartButtonContainerElement = document.createElement('div');
quickchartButtonContainerElement.innerHTML = `{quickchart_button_html}`;
parentElement.appendChild(quickchartButtonContainerElement);
}}
appendQuickchartButton(table);
""".format(
gviz_url=_GVIZ_JS,
data=formatted_data['data'],
Expand All @@ -265,7 +254,6 @@ def _gen_js(self, dataframe):
_DEFAULT_SUPPRESS_OUTPUT_SCROLLING
),
min_width=f'"{self._min_width}"' if self._min_width else 'undefined',
quickchart_button_html=quickchart_button_html,
)


Expand Down