"""SVG drawings for general use."""
# Author(s): Davide.De-Marchi@ec.europa.eu
# Copyright © European Union 2022-2023
#
# Licensed under the EUPL, Version 1.2 or as soon they will be approved by
# the European Commission subsequent versions of the EUPL (the "Licence");
#
# You may not use this work except in compliance with the Licence.
#
# You may obtain a copy of the Licence at:
# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
# Unless required by applicable law or agreed to in writing, software
# distributed under the Licence is distributed on an "AS IS"
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
# express or implied.
#
# See the Licence for the specific language governing permissions and
# limitations under the Licence.
from ipywidgets import HTML, widgets, Layout
from ipyevents import Event
from IPython.display import display
import math
from datetime import datetime
from textwrap import wrap
try:
from . import colors
from .vuetify import fontsettings
from .vuetify import settings
except:
import colors
from vuetify import fontsettings
from vuetify import settings
###########################################################################################################################################################################
# Returns a string containing the legend for a vector layer classification in SVG format
###########################################################################################################################################################################
[docs]def categoriesLegend(title, descriptions, width=200, elemHeight=38, bordercolor='black', borderWidth=2, textcolor='black',
colorlist=['#fef4ef', '#fdd3c0', '#fca080', '#fa6a49', '#e23026', '#b11117', '#66000c'],
dark=settings.dark_mode):
"""
Creation of a legend for categories given title and descriptions of the classes.
Parameters
----------
title : str
Title of the legend
descriptions : list of strings
Description of each of the classe of the legend. If a description contains a '\t' character, the text after it is displayed as multi-line text in smaller font
width : int, optional
Width in pixel of the legend (default is 200)
elemHeight : int, optional
Height in pixels of each of the elements of the legend (default is 38)
bordercolor : str, optional
Color of the border of the legend elements (default is 'black')
borderWidth : int, optional
Width in pixels of the border of the legend elements (default is 2)
textcolor : str, optional
Color of text for the legend elements (default is 'black')
colorlist : list of colors, optional
List of colors to assign to the country polygons (see `Plotly sequential color scales <https://plotly.com/python/builtin-colorscales/#builtin-sequential-color-scales>`_ and `Plotly qualitative color sequences <https://plotly.com/python/discrete-color/#color-sequences-in-plotly-express>`_ )
dark : bool, optional
If True, the bordercolor and textcolor are set to white (default is False)
Return
------
a string containing SVG text to display the legend
Example
-------
Example of the creation of an SVG drawing for a categories legend::
from vois import svgUtils
import plotly.express as px
svg = svgUtils.categoriesLegend("Legend title",
['1:\tVery long class description that can span multiple lines and that contains no info at all',
'Class 2', 'Class 3', 'Class 4'],
colorlist=px.colors.sequential.Blues,
width=250)
display(HTML(svg))
.. figure:: figures/categoriesLegend.png
:scale: 100 %
:alt: categoriesLegend example
Example of a categories legend
"""
def multilineText(x,y,width,height, text):
return '''
<switch>
<foreignObject x="%d" y="%d" width="%d" height="%d">
<div style="display: table-cell; vertical-align: middle; width: %dpx; height: %dpx;">
<p xmlns="http://www.w3.org/1999/xhtml" style="line-height: 14px; font-size: 8pt;">%s</p>
</div>
</foreignObject>
<text x="%d" y="%d">Your SVG viewer cannot display html.</text>
<title>%s</title>
</switch>''' % (x,y,width,height,width,height, text, x,y, text)
if dark:
if bordercolor=='black': bordercolor='white'
if textcolor =='black': textcolor ='white'
x = 0
y = 12
space = 4
n = len(descriptions)
svg = '<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d" version="1.1">\n' % (width, 28+n*(elemHeight+space))
svg += '<text text-anchor="start" x="%d" y="%d" font-size="15" fill="%s" font-weight="500">%s</text>' % (x, y+5, textcolor, title)
y += elemHeight/2
s = colors.colorInterpolator(colorlist, 1, max(2,n))
for i in range(n):
col = s.GetColor(i+1)
text = descriptions[i]
if '\t' in text:
value = text.split('\t')[0]
text = text.split('\t')[1]
svg += '<rect style="fill:%s;" x="%d" y="%d" width="%d" height="%d" stroke="%s" stroke-width="%d"><title>%s</title></rect>\n' % (col, x+1, y, elemHeight-1, elemHeight-1, bordercolor, borderWidth, value)
svg += '<text text-anchor="start" x="%d" y="%d" font-size="15" fill="%s" font-weight="400">%s<title>%s</title></text>' % (x+elemHeight+space, y+elemHeight/2+4, textcolor, value, text)
if len(text) > 0: svg += multilineText(x+2*elemHeight,y-1,width-2*elemHeight-space,elemHeight+10, text)
else:
value = text
svg += '<rect style="fill:%s;" x="%d" y="%d" width="%d" height="%d" stroke="%s" stroke-width="%d"><title>%s</title></rect>\n' % (col, x+1, y, elemHeight-1, elemHeight-1, bordercolor, borderWidth, text)
svg += '<text text-anchor="start" x="%d" y="%d" font-size="15" fill="%s" font-weight="400">%s<title>%s</title></text>' % (x+elemHeight+space, y+elemHeight/2+4, textcolor, value, text)
y += elemHeight + space
svg += '</svg>'
return svg
###########################################################################################################################################################################
# Returns svg string containing a vertical graduated/colors legend
###########################################################################################################################################################################
[docs]def graduatedLegend(df, # Pandas dataframe indexed on code_column and containing 'value' and 'label' columns
code_column=None, # Name of the column containing the code of the country (None = the country code is the index of the dataframe)
value_column='value', # Name of the column containing the value
label_column='label', # Name of the column containing the label
dictnames=None, # Dict to convert codes to names
codes_selected=[], # codes of the countries selected
colorlist=['#0d0887', '#46039f', '#7201a8', '#9c179e', '#bd3786', '#d8576b', '#ed7953', '#fb9f3a', '#fdca26', '#f0f921'], # default color scale
stdevnumber=2.0, # Number of stddev to calculate (minvalue,maxvalue) range
fill='#f1f1f1', # fill color for countries
stroke_selected='#00ffff', # stroke color for border of selected country
decimals=2, # Number of decimals for the legend number display
minallowed_value=None, # Minimum value allowed
maxallowed_value=None, # Maximum value allowed
hoveronempty=False, # If True highlights polygon on hover even if no value present in input df for the polygon
legendtitle='', # Title to add to the legend (top)
legendunits='', # Units of measure to add to the legend (bottom)
fontsize=20,
width=200,
height=600,
bordercolor='black',
textcolor='black',
dark=False):
"""
Creation of graduated legend in SVG format. Given a Pandas DataFrame in the same format of the one in input to :py:func:`interMap.geojsonMap` function, this functions generates an SVG drawing displaying a graduated colors legend.
Parameters
----------
df : Pandas DataFrame
Pandas DataFrame to use for assigning values to features. It has to contain at least a column with numeric values.
code_column : str, optional
Name of the column of the df Pandas DataFrame containing the unique code of the features. This column is used to perform the join with the internal attribute of the geojson vector dataset that contains the unique code. If the code_column is None, the code is taken from the index of the DataFrame, (default is None)
value_column : str, optional
Name of the column of the Pandas DataFrame containing the values to be assigned to the features using the join on geojson unique codes (default is 'value')
label_column : str, optional
Name of the column of the Pandas DataFrame containing the label to be assigned to the features using the join on geojson unique codes (default is 'label')
dictnames : dict, optional
Dictionary to convert codes to names when displaying the selection (default is None)
codes_selected : list of strings, optional
List of codes of features to display as selected (default is [])
colorlist : list of colors, optional
List of colors to assign to the country polygons (default is the Plotly px.colors.sequential.Plasma, see `Plotly sequential color scales <https://plotly.com/python/builtin-colorscales/#builtin-sequential-color-scales>`_ and `Plotly qualitative color sequences <https://plotly.com/python/discrete-color/#color-sequences-in-plotly-express>`_ )
stdevnumber : float, optional
The correspondance between the values assigned to features and the colors list is done by calculating a range of values [min,max] to linearly map the values to the colors. This range is defined by calculating the mean and standard deviation of the country values and applying this formula [mean - stdevnumber*stddev, mean + stdevnumber*stddev]. Default is 2.0
fill : str, optional
Fill color to use for the features that are not joined (default is '#f1f1f1')
stroke_selected : str, optional
Color to use for the selected features (default is '#00ffff')
decimals : int, optional
Number of decimals for the legend numbers display (default is 2)
minallowed_value : float, optional
Minimum value allowed, to force the calculation of the [min,max] range to map the values to the colors
maxallowed_value : float, optional
Maximum value allowed, to force the calculation of the [min,max] range to map the values to the colors
hoveronempty : bool, optional
If True highlights polygon on hover even if no value present in input df for the feature (default is False)
legendtitle : str, optional
Title to add on top of the legend (default is '')
legendunits : str, optional
Units of measure to add to the bottom of the legend (default is '')
fontsize : int, optional
Size in pixels of the font used for texts (default is 20)
width : int, optional
Width of the SVG drawing in pixels (default is 200)
height : int, optional
Height of the SVG drawing in pixels (default is 600)
bordercolor : str, optional
Color for lines and rects of the legend (default is 'black')
textcolor : str, optional
Color for texts of the legend (default is 'black')
dark : bool, optional
If True, the bordercolor and textcolor are set to white (default is False)
Returns
-------
a string containing SVG text to display the graduated legend
Example
-------
Creation of a SVG drawing to display a graduated legend. Input is prepared in the same way of the example provided for the :py:func:`interMap.geojsonMap` function::
import numpy as np
import pandas as pd
import plotly.express as px
from vois import svgMap, svgUtils
countries = svgMap.country_codes
# Generate random values and create a dictionary: key=countrycode, value=random in [0.0,100.0]
d = dict(zip(countries, list(np.random.uniform(size=len(countries),low=0.0,high=100.0))))
# Create a pandas dataframe from the dictionary
df = pd.DataFrame(d.items(), columns=['iso2code', 'value'])
svg = svgUtils.graduatedLegend(df, code_column='iso2code',
codes_selected=['IT', 'FR', 'CH'],
stroke_selected='red',
colorlist=px.colors.sequential.Viridis[::-1],
stdevnumber=2.0,
legendtitle='2020 Total energy consumption',
legendunits='KTOE per 100K inhabit.',
fontsize=18,
width=340, height=600)
display(HTML(svg))
.. figure:: figures/graduatedLegend.png
:scale: 100 %
:alt: graduatedLegend example
Example of a graduatedLegend in SVG
"""
if df.shape[0] <= 0:
minvalue = 1.0
maxvalue = 2.0
else:
mean = df[value_column].mean()
if df.shape[0] <= 1:
minvalue = mean
maxvalue = mean
else:
stddev = df[value_column].std()
valuemin = df[value_column].min()
valuemax = df[value_column].max()
minvalue = mean - stdevnumber*stddev
maxvalue = mean + stdevnumber*stddev
if minvalue < valuemin: minvalue = valuemin
if maxvalue > valuemax: maxvalue = valuemax
if not minallowed_value is None:
if minvalue < minallowed_value: minvalue = minallowed_value
if not maxallowed_value is None:
if maxvalue > maxallowed_value: maxvalue = maxallowed_value
#print(mean,stddev, minvalue, maxvalue)
if minvalue >= maxvalue: maxvalue = minvalue + 1
ci = colors.colorInterpolator(colorlist,minvalue,maxvalue)
if dark:
if bordercolor=='black': bordercolor='white'
if textcolor =='black': textcolor ='white'
# Positioning of the legend
w = width // 5
x1 = (width-w)/2
x2 = x1 + w
wlineette = width // 20
#y1 = height // 18
y1 = 2*fontsize
h = height - int(1.5*y1)
if len(legendunits) > 0:
h -= y1//2
y2 = y1 + h
barthickness = width/120
fontsize1 = fontsize - 1
fontsize2 = fontsize - 2
# Calculate the legend in SVG format
svg = '''
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="%d" height="%d" version="1.1">
<style type="text/css">
@import url('%s');
''' % (width,height,fontsettings.font_url)
# Colors indexed by iso2_code of countries
polycolors = {} # Fill color of the polygon
polyclass = {} # class to assign to the polygon ("country" or "")
polybary = {} # Y coordinate of the bar on the legend highlighted when hover on a country
polyover = {} # True if a value is outside of the legend
polyname = {} # Name assigned to the polygon
country_codes = df[code_column].unique()
for c in country_codes:
polycolors[c] = fill
polybary[c] = -1000
polyover[c] = False
if hoveronempty:
polyclass[c] = 'country'
else:
polyclass[c] = ''
# Set colors for all the countries
for index, row in df.iterrows():
if code_column is None: code = index
else: code = row[code_column]
polyname[code] = code
if not dictnames is None and code in dictnames:
polyname[code] = dictnames[code]
value = row[value_column]
polycolors[code] = ci.GetColor(value)
polyclass[code] = 'country'
if label_column in df:
v = row[label_column]
else:
v = str(value)
y = y2 - (y2-y1) * (value - minvalue) / (maxvalue - minvalue)
if y < y1: y = y1
if y > y2: y = y2
polybary[code] = y
if value < minvalue or value > maxvalue:
polyover[code] = True
#print(minvalue,maxvalue, y1, y2)
# Add color for every polygon
for c in country_codes:
svg += 'svg #%s { fill: %s; }\n' % (c, polycolors[c])
svg += '</style>'
# Legend on the right
if len(legendtitle) > 0:
svg += '<text x="%d" y="%d" text-anchor="middle" font-size="%f" font-family="%s" font-weight="bold" fill="%s">%s</text>' % (x1+w/2.0, y1-fontsize, fontsize, fontsettings.font_name, textcolor, legendtitle)
if len(legendunits) > 0:
svg += '<text x="%d" y="%d" text-anchor="middle" font-size="%f" font-family="%s" font-weight="bold" fill="%s">%s</text>' % (x1+w/2.0, int(y2+fontsize*1.5), fontsize2, fontsettings.font_name, textcolor, legendunits)
svg += '<rect x="%d" y="%d" width="%d" height="%d" style="fill:none; stroke-width:%f; stroke:%s;" />' % (x1, y1, w, h+1, barthickness*2, bordercolor)
y = y2
for i in range(h):
value = maxvalue - (y - y1) * (maxvalue - minvalue) / (y2 - y1)
svg += '<line x1="%d" y1="%d" x2="%d" y2="%d" style="stroke:%s;stroke-width:%f" />' % ( x1,y,x2,y, ci.GetColor(value), barthickness )
y -= 1
svg += '<line x1="%d" y1="%d" x2="%d" y2="%d" style="stroke:%s; stroke-width:%f" />' % ( x2,y2+3,x2+wlineette,y2+3, bordercolor, barthickness/2.0 )
svg += '<line x1="%d" y1="%d" x2="%d" y2="%d" style="stroke:%s; stroke-width:%f" />' % ( x2,y1-2,x2+wlineette,y1-2, bordercolor, barthickness/2.0 )
valmin = '{:.{prec}f}'.format(minvalue, prec=decimals)
svg += '<text x="%d" y="%d" font-size="%f" font-family="%s" fill="%s">%s</text>' % (x2+wlineette+5, y2+fontsize2/3, fontsize1, fontsettings.font_name, textcolor, valmin)
valmax = '{:.{prec}f}'.format(maxvalue, prec=decimals)
svg += '<text x="%d" y="%d" font-size="%f" font-family="%s" fill="%s">%s</text>' % (x2+wlineette+5, int(y1+fontsize2*0.4), fontsize1, fontsettings.font_name, textcolor, valmax)
valmed = '{:.{prec}f}'.format((minvalue+maxvalue)/2.0, prec=decimals)
if valmed != valmin and valmed != valmax:
y = (y1+y2)/2.0
svg += '<line x1="%d" y1="%f" x2="%d" y2="%f" style="stroke:%s; stroke-width:%f" />' % ( x2,y,x2+wlineette,y, bordercolor, barthickness/2.0 )
svg += '<text x="%d" y="%d" font-size="%f" font-family="%s" fill="%s">%s</text>' % (x2+wlineette+5, y+fontsize2/3, fontsize1, fontsettings.font_name, textcolor, valmed)
val = '{:.{prec}f}'.format(minvalue + 3.0*(maxvalue-minvalue)/4.0, prec=decimals)
if val != valmed and val != valmax:
y = y1+(y2-y1)/4.0
svg += '<line x1="%d" y1="%f" x2="%d" y2="%f" style="stroke:%s; stroke-width:%f" />' % ( x2,y,x2+wlineette,y, bordercolor, barthickness/2.0 )
svg += '<text x="%d" y="%d" font-size="%f" font-family="%s" fill="%s">%s</text>' % (x2+wlineette+5, y+fontsize2/3, fontsize1, fontsettings.font_name, textcolor, val)
val = '{:.{prec}f}'.format(minvalue + (maxvalue-minvalue)/4.0, prec=decimals)
if val != valmin and val != valmed:
y = y1+3.0*(y2-y1)/4.0
svg += '<line x1="%d" y1="%f" x2="%d" y2="%f" style="stroke:%s; stroke-width:%f" />' % ( x2,y,x2+wlineette,y, bordercolor, barthickness/2.0 )
svg += '<text x="%d" y="%d" font-size="%f" font-family="%s" fill="%s">%s</text>' % (x2+wlineette+5, y+fontsize2/3, fontsize1, fontsettings.font_name, textcolor, val)
# Add horizontal lines in the legend for the selected countries
for code in codes_selected:
if code in country_codes:
if polybary[code] >= y1 and polybary[code] <= y2:
svg += '<text x="%f" y="%f" text-anchor="end" font-size="%f" font-family="%s" font-weight="bold" fill="%s">%s<title>%s</title></text>' % (x1-wlineette/2, polybary[code]+fontsize2/3, fontsize2, fontsettings.font_name, textcolor, polyname[code], polyname[code])
dash = ''
if polyover[code]:
dash = 'stroke-dasharray="%f,%f"' % (wlineette*1.1, wlineette*0.5)
svg += '<line x1="%d" y1="%d" x2="%d" y2="%d" style="stroke:%s; stroke-width:%f" %s />' % (x1, polybary[code], x2, polybary[code], stroke_selected, barthickness, dash)
svg += '</svg>'
return svg
###########################################################################################################################################################################
# Returns svg string containing a vertical graduated/colors legend
###########################################################################################################################################################################
[docs]def graduatedLegendVWVH(df, # Pandas dataframe indexed on code_column and containing 'value' and 'label' columns
code_column=None, # Name of the column containing the code of the country (None = the country code is the index of the dataframe)
value_column='value', # Name of the column containing the value
label_column='label', # Name of the column containing the label
codes_selected=[], # codes of the countries selected
colorlist=['#0d0887', '#46039f', '#7201a8', '#9c179e', '#bd3786', '#d8576b', '#ed7953', '#fb9f3a', '#fdca26', '#f0f921'], # default color scale
stdevnumber=2.0, # Number of stddev to calculate (minvalue,maxvalue) range
fill='#f1f1f1', # fill color for countries
stroke_selected='#00ffff', # stroke color for border of selected country
decimals=2, # Number of decimals for the legend number display
minallowed_value=None, # Minimum value allowed
maxallowed_value=None, # Maximum value allowed
hoveronempty=False, # If True highlights polygon on hover even if no value present in input df for the polygon
legendtitle='', # Title to add to the legend (top)
legendunits='', # Units of measure to add to the legend (bottom)
fontsize=20,
width=20.0,
height=40.0,
bordercolor='black',
textcolor='black',
dark=False):
"""
Creation of graduated legend in SVG format. Given a Pandas DataFrame in the same format of the one in input to :py:func:`interMap.geojsonMap` function, this functions generates an SVG drawing displaying a graduated colors legend.
Parameters
----------
df : Pandas DataFrame
Pandas DataFrame to use for assigning values to features. It has to contain at least a column with numeric values.
code_column : str, optional
Name of the column of the df Pandas DataFrame containing the unique code of the features. This column is used to perform the join with the internal attribute of the geojson vector dataset that contains the unique code. If the code_column is None, the code is taken from the index of the DataFrame, (default is None)
value_column : str, optional
Name of the column of the Pandas DataFrame containing the values to be assigned to the features using the join on geojson unique codes (default is 'value')
label_column : str, optional
Name of the column of the Pandas DataFrame containing the label to be assigned to the features using the join on geojson unique codes (default is 'label')
codes_selected : list of strings, optional
List of codes of features to display as selected (default is [])
colorlist : list of colors, optional
List of colors to assign to the country polygons (default is the Plotly px.colors.sequential.Plasma, see `Plotly sequential color scales <https://plotly.com/python/builtin-colorscales/#builtin-sequential-color-scales>`_ and `Plotly qualitative color sequences <https://plotly.com/python/discrete-color/#color-sequences-in-plotly-express>`_ )
stdevnumber : float, optional
The correspondance between the values assigned to features and the colors list is done by calculating a range of values [min,max] to linearly map the values to the colors. This range is defined by calculating the mean and standard deviation of the country values and applying this formula [mean - stdevnumber*stddev, mean + stdevnumber*stddev]. Default is 2.0
fill : str, optional
Fill color to use for the features that are not joined (default is '#f1f1f1')
stroke_selected : str, optional
Color to use for the border of the selected features (default is '#00ffff')
decimals : int, optional
Number of decimals for the legend numbers display (default is 2)
minallowed_value : float, optional
Minimum value allowed, to force the calculation of the [min,max] range to map the values to the colors
maxallowed_value : float, optional
Maximum value allowed, to force the calculation of the [min,max] range to map the values to the colors
hoveronempty : bool, optional
If True highlights polygon on hover even if no value present in input df for the feature (default is False)
legendtitle : str, optional
Title to add on top of the legend (default is '')
legendunits : str, optional
Units of measure to add to the bottom of the legend (default is '')
fontsize : int, optional
Size in pixels of the font used for texts (default is 20)
width : float, optional
Width of the SVG drawing in vw units (default is 20.0)
height : float, optional
Height of the SVG drawing in vh units (default is 40.0)
bordercolor : str, optional
Color for lines and rects of the legend (default is 'black')
textcolor : str, optional
Color for texts of the legend (default is 'black')
dark : bool, optional
If True, the bordercolor and textcolor are set to white (default is False)
Returns
-------
a string containing SVG text to display the graduated legend
Example
-------
Creation of a SVG drawing to display a graduated legend. Input is prepared in the same way of the example provided for the :py:func:`interMap.geojsonMap` function::
import numpy as np
import pandas as pd
import plotly.express as px
from vois import svgMap, svgUtils
countries = svgMap.country_codes
# Generate random values and create a dictionary: key=countrycode, value=random in [0.0,100.0]
d = dict(zip(countries, list(np.random.uniform(size=len(countries),low=0.0,high=100.0))))
# Create a pandas dataframe from the dictionary
df = pd.DataFrame(d.items(), columns=['iso2code', 'value'])
svg = svgUtils.graduatedLegend(df, code_column='iso2code',
codes_selected=['IT', 'FR', 'CH'],
stroke_selected='red',
colorlist=px.colors.sequential.Viridis[::-1],
stdevnumber=2.0,
legendtitle='2020 Total energy consumption',
legendunits='KTOE per 100K inhabit.',
fontsize=18,
width=340, height=600)
display(HTML(svg))
.. figure:: figures/graduatedLegend.png
:scale: 100 %
:alt: graduatedLegend example
Example of a graduatedLegend in SVG
"""
if df.shape[0] <= 0:
minvalue = 1.0
maxvalue = 2.0
else:
mean = df[value_column].mean()
if df.shape[0] <= 1:
minvalue = mean
maxvalue = mean
else:
stddev = df[value_column].std()
valuemin = df[value_column].min()
valuemax = df[value_column].max()
minvalue = mean - stdevnumber*stddev
maxvalue = mean + stdevnumber*stddev
if minvalue < valuemin: minvalue = valuemin
if maxvalue > valuemax: maxvalue = valuemax
if not minallowed_value is None:
if minvalue < minallowed_value: minvalue = minallowed_value
if not maxallowed_value is None:
if maxvalue > maxallowed_value: maxvalue = maxallowed_value
#print(mean,stddev, minvalue, maxvalue)
if minvalue >= maxvalue: maxvalue = minvalue + 1
ci = colors.colorInterpolator(colorlist,minvalue,maxvalue)
if dark:
if bordercolor=='black': bordercolor='white'
if textcolor =='black': textcolor ='white'
# Sizable dimensioning
svgwidth = 100.0
aspectratio = 0.5*height / width # In landscape mode, usually the height is half the width dimension!!!
svgheight = svgwidth * aspectratio
# Positioning of the legend
w = svgwidth / 5.0
x1 = (svgwidth-w)/2
x2 = x1 + w
wlineette = svgwidth / 25.0
#y1 = height // 18
y1 = 2*fontsize
h = svgheight - 1.5*y1
if len(legendunits) > 0:
h -= y1/2.0
y2 = y1 + h
barthickness = width/50.0
fontsize1 = fontsize*0.92
fontsize2 = fontsize*0.85
# Calculate the legend in SVG format
preserve = 'xMidYMid meet'
svg = '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" viewBox="0 0 %f %f" preserveAspectRatio="%s" width="%fvw" height="%fvh">' % (svgwidth,svgheight, preserve, width,height)
svg += '''
<style type="text/css">
@import url('%s');
''' % fontsettings.font_url
# Colors indexed by iso2_code of countries
polycolors = {} # Fill color of the polygon
polyclass = {} # class to assign to the polygon ("country" or "")
polybary = {} # Y coordinate of the bar on the legend highlighted when hover on a country
country_codes = df[code_column].unique()
for c in country_codes:
polycolors[c] = fill
polybary[c] = -1000
if hoveronempty:
polyclass[c] = 'country'
else:
polyclass[c] = ''
# Set colors for all the countries
for index, row in df.iterrows():
if code_column is None: code = index
else: code = row[code_column]
value = row[value_column]
polycolors[code] = ci.GetColor(value)
polyclass[code] = 'country'
if label_column in df:
v = row[label_column]
else:
v = str(value)
y = y2 - (y2-y1) * (value - minvalue) / (maxvalue - minvalue)
if y < y1: y = y1
if y > y2: y = y2
polybary[code] = y
#print(minvalue,maxvalue, y1, y2)
# Add color for every polygon
for c in country_codes:
svg += 'svg #%s { fill: %s; }\n' % (c, polycolors[c])
svg += '</style>'
# Legend on the right
if len(legendtitle) > 0:
svg += '<text x="%d" y="%d" text-anchor="middle" font-size="%f" font-family="%s" font-weight="bold" fill="%s">%s</text>' % (x1+w/2.0, y1-fontsize, fontsize, fontsettings.font_name, textcolor, legendtitle)
if len(legendunits) > 0:
svg += '<text x="%f" y="%f" text-anchor="middle" font-size="%f" font-family="%s" font-weight="bold" fill="%s">%s</text>' % (x1+w/2.0, y2+fontsize*1.5, fontsize2, fontsettings.font_name, textcolor, legendunits)
svg += '<rect x="%f" y="%f" width="%f" height="%f" style="fill:none; stroke-width:%f; stroke:%s;" />' % (x1, y1, w, h, barthickness*4.0, bordercolor)
y = y1
while y <= y2:
value = maxvalue - (y - y1) * (maxvalue - minvalue) / (y2 - y1)
svg += '<line x1="%f" y1="%f" x2="%f" y2="%f" style="stroke:%s;stroke-width:%f" />' % ( x1,y,x2,y, ci.GetColor(value), barthickness )
y += 0.06666
svg += '<line x1="%f" y1="%f" x2="%f" y2="%f" style="stroke:%s; stroke-width:%f" />' % ( x2,y2,x2+wlineette,y2, bordercolor, barthickness/2.0 )
svg += '<line x1="%f" y1="%f" x2="%f" y2="%f" style="stroke:%s; stroke-width:%f" />' % ( x2,y1,x2+wlineette,y1, bordercolor, barthickness/2.0 )
xtext = x2 + wlineette*1.25
valmin = '{:.{prec}f}'.format(minvalue, prec=decimals)
svg += '<text x="%f" y="%f" font-size="%f" font-family="%s" fill="%s">%s</text>' % (xtext, y2+fontsize2/3, fontsize1, fontsettings.font_name, textcolor, valmin)
valmax = '{:.{prec}f}'.format(maxvalue, prec=decimals)
svg += '<text x="%f" y="%f" font-size="%f" font-family="%s" fill="%s">%s</text>' % (xtext, int(y1+fontsize2*0.4), fontsize1, fontsettings.font_name, textcolor, valmax)
valmed = '{:.{prec}f}'.format((minvalue+maxvalue)/2.0, prec=decimals)
if valmed != valmin and valmed != valmax:
y = (y1+y2)/2.0
svg += '<line x1="%f" y1="%f" x2="%f" y2="%f" style="stroke:%s; stroke-width:%f" />' % ( x2,y,x2+wlineette,y, bordercolor, barthickness/2.0 )
svg += '<text x="%f" y="%f" font-size="%f" font-family="%s" fill="%s">%s</text>' % (xtext, y+fontsize2/3.0, fontsize1, fontsettings.font_name, textcolor, valmed)
val = '{:.{prec}f}'.format(minvalue + 3.0*(maxvalue-minvalue)/4.0, prec=decimals)
if val != valmed and val != valmax:
y = y1+(y2-y1)/4.0
svg += '<line x1="%f" y1="%f" x2="%f" y2="%f" style="stroke:%s; stroke-width:%f" />' % ( x2,y,x2+wlineette,y, bordercolor, barthickness/2.0 )
svg += '<text x="%f" y="%f" font-size="%f" font-family="%s" fill="%s">%s</text>' % (xtext, y+fontsize2/3.0, fontsize1, fontsettings.font_name, textcolor, val)
val = '{:.{prec}f}'.format(minvalue + (maxvalue-minvalue)/4.0, prec=decimals)
if val != valmin and val != valmed:
y = y1+3.0*(y2-y1)/4.0
svg += '<line x1="%f" y1="%f" x2="%f" y2="%f" style="stroke:%s; stroke-width:%f" />' % ( x2,y,x2+wlineette,y, bordercolor, barthickness/2.0 )
svg += '<text x="%f" y="%f" font-size="%f" font-family="%s" fill="%s">%s</text>' % (xtext, y+fontsize2/3.0, fontsize1, fontsettings.font_name, textcolor, val)
# Add horizontal lines in the legend for the selected countries
for code in codes_selected:
if code in country_codes:
if polybary[code] >= y1 and polybary[code] <= y2:
svg += '<text x="%f" y="%f" text-anchor="end" font-size="%f" font-family="%s" font-weight="bold" fill="%s">%s</text>' % (x1-wlineette/2, polybary[code]+fontsize2/3, fontsize2, fontsettings.font_name, textcolor, code)
svg += '<line x1="%f" y1="%f" x2="%f" y2="%f" style="stroke:%s; stroke-width:%f" />' % (x1, polybary[code], x2, polybary[code], stroke_selected, barthickness*3.0)
svg += '</svg>'
return svg
###########################################################################################################################################################################
# https://www.smashingmagazine.com/2019/01/html5-svg-fill-animation-css3-vanilla-javascript/
# Small circle with animation: returns a widgets.Output object with the SVG displayed on it
###########################################################################################################################################################################
[docs]def SmallCircle(text1, text2, percentage, forecolor="#308040", backcolor=None, textcolor='white', dimension=300.0, fontsize=16.0, textsize=0.0):
"""
Display of a circle graphics displaying a text and a percentage value. It shows an animation to reach the requested percentage of the full circle.
Parameters
----------
text1 : str
First string of text to display inside the circle (usually a brief description of the variable displayed)
text2 : str
Second string of text to display inside the circle (usually the numercal value)
percentage : float
Value in percentage to be displayed
forecolor : str, optional
Color to use for the circle border (default is '#308040')
backcolor : str, optional
Color to use for the interior of the circle (default is None, so a lighter color is generated from the forecolor)
textcolor : str, optional
Color to use for text (default is 'white')
dimension : int or float or str, optional
Side of the drawing. If an integer or a float is passed, the size is intended in pixels units, otherwise a string containing the units must be passed (example: '4vh'). The default is 300.0 for 300 pixels
textsize : float, optional
Text dimension in pixels (default is 0.0 which means that it is automatically calculated from the dimension of the drawing)
Returns
-------
an ipywidgets.Output instance with the circle already displayed inside
Example
-------
Example of a circle to represent a percentage with an animation::
from vois import svgUtils
from random import randrange
percentage = randrange(1000)/10.0
svgUtils.SmallCircle('Green<br>deal',
'%.1f%%' % percentage,
percentage,
forecolor="#308040",
dimension=200)
.. figure:: figures/smallCircle.png
:scale: 100 %
:alt: smallCircle example
Example of an animated SVG to graphically represent a percentage value
"""
def circumference(r):
return 2.0 * r * math.pi
def complementaryColor(color):
if color[0] == '#':
color = color[1:]
rgb = (color[0:2], color[2:4], color[4:6])
comp = ['%02X' % (255 - int(a, 16)) for a in rgb]
return '#' + ''.join(comp)
def lighterColor(color):
if color[0] == '#':
color = color[1:]
rgb = (color[0:2], color[2:4], color[4:6])
comp = ['%02X' % min(255,int(int(a, 16)*1.5)) for a in rgb]
return '#' + ''.join(comp)
if backcolor is None:
backcolor = lighterColor(forecolor)
tooltip = text1.replace('<br>',' ') + ': ' + text2
if percentage < 0.0: percentage = 0.0
if percentage > 100.0: percentage = 100.0
r = 44
circle = circumference(r)
#value = 276.46015351590177 # empty
#value = 0.0 # full
value = 0.01 * (100.0 - percentage)*circle
seconds = 1.0 + (circle - value) / circle
svg = '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 100 100" xml:space="preserve">'
# CSS styling
svg += '''
<style type="text/css">
@import url('%s');
</style>
''' % (fontsettings.font_url)
svg += '<circle fill="%s" cx="50" cy="50" r="44"><title>%s</title></circle>' % (backcolor,tooltip)
svg += '<circle fill="none" stroke="#f1f1f1" stroke-width="10" stroke-mitterlimit="0" cx="50" cy="50" r="%d"><title>%s</title></circle>' % (r,tooltip)
svg += '''
<circle fill="none" stroke="%s" stroke-width="10.5" stroke-mitterlimit="0" cx="50" cy="50" r="%d" stroke-dasharray="%d" stroke-dashoffset="%d" stroke-linecap="butt" transform="rotate(-90 ) translate(-100 0)">
<animate attributeName="stroke-dashoffset" values="%d;%d;%d" dur="%fs"></animate>
<title>%s</title>
</circle>
''' % (forecolor, r, circle, value, circle, value, value, seconds, tooltip)
vtext = text1.split('<br>')
maxlen = 0
for t in vtext:
if len(t) > maxlen: maxlen = len(t)
if textsize <= 0: size = 1.0 * 10.0 / float(maxlen)
else: size = textsize
if size > 0.9: size = 0.9
if size < 0.4: size = 0.4
h = int(14 * size)
y = 47 - (len(vtext)-1)*h
for t in vtext:
svg += '<text font-size="%.2fem" text-anchor="middle" x="50" y="%d" fill="%s" font-weight="bold" style="font-family: %s;">%s<title>%s</title></text>' % (size,y, textcolor, fontsettings.font_name,t,tooltip)
y += h
svg += '<text font-size="1em" text-anchor="middle" x="50" y="62" fill="%s" font-weight="bold" style="font-family: %s;">%s<title>%s</title></text>' % (textcolor, fontsettings.font_name, text2, tooltip)
svg += '</svg>'
# Create an output widget and display SVG in it
if isinstance(dimension, int):
w = '%dpx' % dimension
elif isinstance(dimension, float):
w = '%fpx' % dimension
else:
w = str(dimension)
out = widgets.Output(layout=Layout(width=w, height='calc(%s + 14px)' % w))
with out:
display(HTML(svg))
return out
###########################################################################################################################################################################
# https://mirellavanteulingen.nl/blog/svg-charts-animations.html
# Pie chart with animation: returns a widgets.Output object with the SVG displayed on it + the svg text
###########################################################################################################################################################################
# Enumeration
class FillPercentage():
fill100 = 0
fill75 = 1
fill60 = 2
fill50 = 3
fill40 = 4
[docs]def AnimatedPieChart(values=[10.0, 25.0, 34.0, 24.0, 23.0], colors=['#2d82c2', '#95cb92', '#e7ee99', '#ffde88', '#ff945a', '#e34e4f'], labels=None, duration=0.75, # Seconds
fillpercentage=FillPercentage.fill60, backcolor="#f1f1f1", dimension=400,
textcolor=settings.select_textcolor, fontsize=15, textweight=400, decimals=1,
centertext='', centercolor=settings.select_textcolor, centerfontsize=18, centertextweight=500,
onclick=None, additional_argument=None, is_selected=False, displayvalues=True):
"""
Creation of an animated pie chart in SVG format. Given an array of float values, and optional labels, the function draws a pie chart that fills its slices with a short animation. An ipywidgets.Output instance is returned, which has the SVG chart displayed in it. By passing a value to the onclick parameter, it is possible to manage the click event on the slices of the pie, providing interactivity to the drawing. The capture of the click event is done using the `ipyevents library <https://github.com/mwcraig/ipyevents>`_ .
Parameters
----------
values : list of float values, optional
List of float values that represent the relative dimension of each of the slices (default is [10.0, 25.0, 34.0, 24.0, 23.0])
colors : list of strings representing colors, optional
Colors to use for each of the slices of the pie (default is ['#2d82c2', '#95cb92', '#e7ee99', '#ffde88', '#ff945a', '#e34e4f'])
labels, list of strings, optional
Labels for each of the slices of the pie (default is None)
duration : float, optional
Duration in seconds of the animation (default is 0.75 seconds)
fillpercentage : int, optional (in 0,1,2,3,4)
Amount of the circle that is filled by the slices (default is 2 which means 60%)
backcolor : str, optional
Background color of the pie (default is '#f1f1f1')
dimension : int or float or str, optional
Side of the drawing. If an integer or a float is passed, the size is intended in pixels units, otherwise a string containing the units must be passed (example: '4vh'). Th default is 400.0 for 400 pixels
textcolor : str, optional
Color of text
fontsize : int, optional
Dimension of text in pixels (default is 15)
textweight : int, optional
Weight of text (default is 400, >= 500 is Bold)
decimals : int, optional
Number of decimal to use for the display of numbers (default is 1)
centertext : str, optional
Text string to display at the center of the pie (default is '')
centercolor : str, optional
Color to use for the central text
centerfontsize : int, optional
Text dimension for the text displayed at the center of the pie
centertextweight : int, optional
Weight of central text (default is 500, >= 500 is Bold)
onclick : function, optional
Python function to call when the user clicks on one of the slices of the pie. The function will receive as first parameter the index of the clicked slice, and the additional_argument as second parameter
additional_argument : any, optional
Additional parameter passed to the onclick function when the user clicks on one of the slices of the pie (default is None)
is_selected : bool, optional
Flag to select the pie chart (default is False)
displayvalues: bool, optional
If True each slide of the pie will display, inside parenthesis, the corresponding value (default is True)
Return
------
a tuple containing an instance of ipywidgets.Output() widget, and a string containing the SVG code of the drawing
Example
-------
Example of a pie chart::
from vois import svgUtils
import plotly.express as px
from ipywidgets import widgets
debug = widgets.Output()
display(debug)
def onclick(arg):
with debug:
print('clicked %s' % arg)
out, txt = svgUtils.AnimatedPieChart(values=[10.0, 25.0, 18.0, 20.0, 9.5],
labels=['Option<br>1', 'Option<br>2',
'Option 3', 'Option 4',
'Others'],
centerfontsize=28,
fontsize=16, textweight=400,
colors=px.colors.qualitative.D3,
backcolor='#dfdfdf',
centertext='Example Pie',
onclick=onclick,
dimension=380.0,
duration=1.0)
display(out)
.. figure:: figures/pieChart2.png
:scale: 100 %
:alt: pieChart example
Example of an animated SVG to graphically represent a pie chart
"""
# From polar to cartesian coordinates
def polar2cart(r, phi):
arad = math.radians(-phi)
x = r * math.cos(arad)
y = r * math.sin(arad)
return x,-y
# From cartesian to polar coordinates: returns r, theta(degrees)`
def cart2polar(x,y):
return math.hypot(x,y), math.degrees(math.atan2(y,x))
# Calculate text position: returns x,y
def textPosition(angle, r):
while angle > 360: angle -= 360
while angle < 0: angle += 360
return polar2cart(r, angle)
if fillpercentage == FillPercentage.fill100:
textdistance = 120
width = 100.0
elif fillpercentage == FillPercentage.fill75:
textdistance = 125
width = 75.0
elif fillpercentage == FillPercentage.fill60:
textdistance = 140
width = 60.0
elif fillpercentage == FillPercentage.fill50:
textdistance = 150
width = 50.0
else:
textdistance = 170
width = 40.0
svgdimension = 400
# Centre and external radius of the circle
cx = svgdimension/2
cy = svgdimension/2
externalradius = svgdimension/2
# Internal Radius
r = 200.0 - width
stroke = width * 2.0 - 10.0
# Percentages calculation
tot = sum(values)
perc = [100.0*x/tot for x in values]
# Pre-processing of all labels
if not labels is None:
for i in range(len(labels)):
labels[i] = labels[i].replace('-',' ').replace('_',' ')
# Calculation of all coordinates and dimensioning: key is the index of the sector, from 0 to len(values)-1
coordinates = {}
startangle = -90.0
for i in range(len(perc)):
angle = 360.0 * perc[i] / 100.0
# Sorry!
if fillpercentage == FillPercentage.fill100:
span = 628.8 * perc[i] / 100.0
elif fillpercentage == FillPercentage.fill75:
span = 790.0 * perc[i] / 100.0
elif fillpercentage == FillPercentage.fill60:
span = 890.0 * perc[i] / 100.0
elif fillpercentage == FillPercentage.fill50:
span = 945.0 * perc[i] / 100.0
else:
span = 1010.0 * perc[i] / 100.0
color = colors[i % len(colors)]
color_no_answer = '#757575'
strvalue = '{:.{prec}f}'.format(perc[i], prec=decimals)
if not labels is None and i < len(labels):
if displayvalues:
text = '%s<br>%s%%<br>(%s)' % (labels[i],strvalue,values[i])
else:
text = '%s<br>%s%%' % (labels[i],strvalue)
if text[:9] == 'no answer' or text[:12] == 'no<br>answer': color = color_no_answer # Request for same color for all the "no answer" slices
#print(text)
else:
if displayvalues:
text = strvalue + '<br>(' + str(values[i])+ ')'
else:
text = strvalue
fulltext = centertext + ': ' + text.replace('<br>',' ')
tooltip = '<title>%s</title>' % (fulltext)
x,y = textPosition(startangle+angle/2.0, textdistance)
svgtext = ''
if perc[i] > 5.0:
vtext = text.split('<br>')
y -= fontsize * (len(vtext) - 1.0)
svgtext = '<text id="text" class="portion-text" style="pointer-events: none" font-size="%f" text-anchor="middle" x="%f" y="%f" fill="%s" font-weight="%f" style="font-family: %s;">' % (fontsize, x+200,y+225, textcolor, textweight, fontsettings.font_name)
for t in vtext:
svgtext += '<tspan x="%f" dy="%f">%s</tspan>' % (x+200, fontsize, t)
svgtext += '</text>'
coordinates[i] = {
'startangle': startangle,
'angle' : angle,
'span' : span,
'color' : color,
'text' : text,
'tooltip' : tooltip,
'x' : x,
'y' : y,
'svgtext' : svgtext
}
startangle += angle
underline = ''
if is_selected:
class_name = 'portion-out'
underline = 'text-decoration="underline"'
centertextweight = 700
#centercolor = settings.color_first
else:
class_name = 'portion'
# Creation of the SVG
def createSVG():
svg = '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 %d %d" xml:space="preserve">' % (svgdimension,svgdimension)
# CSS styling
svg += '''
<style type="text/css">
@import url('%s');
.portion:hover , .portion:hover + .portion-text {
cursor: pointer;
stroke-width: %f;
font-weight: %d;
text-decoration: underline;
}
</style>
''' % (fontsettings.font_url, stroke*1.1, textweight+0)
# Back circle
strclass = ''
if is_selected:
strclass = 'class="portion-out"'
svg += '<circle %s fill="%s" cx="200" cy="200" r="200" />' % ( strclass, backcolor )
if is_selected:
svg += '<circle %s fill="%s" cx="200" cy="200" r="85" />' % ( strclass, settings.color_first )
# Animated circles
svgtextall = ''
for i in range(len(perc)):
angle = coordinates[i]['angle']
startangle = coordinates[i]['startangle']
span = coordinates[i]['span']
color = coordinates[i]['color']
text = coordinates[i]['text']
tooltip = coordinates[i]['tooltip']
x = coordinates[i]['x']
y = coordinates[i]['y']
# Text
svgtext = coordinates[i]['svgtext']
svgtextall += svgtext
# Circle sector
if duration <= 0.0:
svg += '''
<circle class="%s" cx="%d" cy="%d" r="%f" transform="rotate(%f, 200, 200)" stroke-dasharray="%d, 1000" fill="none" stroke-width="%f" stroke="%s" stroke-linecap="butt">%s</circle>
%s
''' % (class_name, cx,cy, r, startangle, int(span), stroke, color, tooltip, svgtext)
else:
svg += '''
<circle class="%s" cx="%d" cy="%d" r="%f" transform="rotate(%f, 200, 200)" stroke-dasharray="0, 1000" fill="none" stroke-width="%f" stroke="%s" stroke-linecap="butt">
<animate attributeName="stroke-dasharray" dur="%fs" to="%d,1000" fill="freeze" />%s</circle>
%s
''' % (class_name, cx,cy, r, startangle, stroke, color, duration, int(span), tooltip, svgtext)
# Center text
svg += '<text style="pointer-events: none" font-size="%f" text-anchor="middle" x="200" y="%d" fill="%s" font-weight="%f" %s style="font-family: %s;">%s</text>' % (centerfontsize, 200+fontsize/4, centercolor, centertextweight, underline, fontsettings.font_name, centertext)
# Display text again so that it is always visible!!!
svg += svgtextall
svg += '</svg>'
return svg
# Create an output widget and display SVG in it
if isinstance(dimension, int):
w = '%dpx' % dimension
elif isinstance(dimension, float):
w = '%fpx' % dimension
else:
w = str(dimension)
out = widgets.Output(layout=Layout(width=w, height='calc(%s + 14px)' % w))
svg = createSVG()
with out:
display(HTML(svg))
# Add an event manager to the out Output widgets
d = Event(source=out, watched_events=['click'])
cx = svgdimension/2.0
cy = svgdimension/2.0
radius = svgdimension/2.0
if fillpercentage == FillPercentage.fill100:
rmin = 0.0
elif fillpercentage == FillPercentage.fill75:
rmin = radius * 0.25
elif fillpercentage == FillPercentage.fill60:
rmin = radius * 0.40
elif fillpercentage == FillPercentage.fill50:
rmin = radius * 0.50
else:
rmin = 1.0
# Convert clik angle to startangle values
def clickangle2startangle(a):
# Convert in [0,360)
if a < 0:
a += 360.0
# Invert direction
sa = -a
# Take inside interval [-90,270)
if sa < -90.0:
sa += 360.0
return sa
def handle_event(event):
if is_selected:
# Call callback function
if not onclick is None:
if not additional_argument is None:
onclick(-1, additional_argument)
else:
onclick(-1)
out.clear_output(wait=True)
with out:
display(HTML(createSVG()))
else:
x = event['relativeX']
y = event['relativeY']
w = event['boundingRectWidth']
h = event['boundingRectHeight']
xp = (x / w) * svgdimension
yp = (y / h) * svgdimension
px = xp - cx
py = cy - yp
#with out:
# print(x,y, xp,yp )
clickdist,clickangle = cart2polar(px,py)
if clickdist >= rmin and clickdist <= radius:
sa = clickangle2startangle(clickangle)
#with out:
# print(clickangle, sa)
for i in range(len(perc)):
angle = coordinates[i]['angle']
startangle = coordinates[i]['startangle']
if sa >= startangle and sa <= startangle+angle:
# Call callback function
if not onclick is None:
if not additional_argument is None:
onclick(i, additional_argument)
else:
onclick(i)
#out.clear_output(wait=True)
#with out:
# display(HTML(createSVG()))
break
d.on_dom_event(handle_event)
return out, svg
###########################################################################################################################################################################
# Title
###########################################################################################################################################################################
[docs]def svgTitle(title='Dashboard title', subtitle1='Subtitle1', subtitle2='Subtitle2', xline=290):
"""
Creation of a simple title for a dashboard in SVG format
Parameters
----------
title : str, optional
Title of the dashboard (default is 'Dashboard title')
subtitle1 : str, optional
First line of the subtitle (default is 'Subtitle1')
subtitle2 : str, optional
Second line of the subtitle (default is 'Subtitle2')
xline : int, optional
X position of a vertical line to divide title from subtitles (default is 290)
Returns
-------
a string containing a SVG drawing that spans 100% of the width and 46 pixels in height
Example
-------
Example of a title and logo SVG::
from vois import svgUtils
from ipywidgets import HTML, widgets, Layout
outTitle = widgets.Output(layout=Layout(width='99%', height='64px'))
outLogo = widgets.Output(layout=Layout(width='1%', min_width='110px', height='82px'))
outTitle.clear_output()
with outTitle:
display(HTML(svgUtils.svgTitle()))
outLogo.clear_output()
with outLogo:
display(HTML(svgUtils.svgLogo()))
display(widgets.HBox([outTitle,outLogo]))
.. figure:: figures/svgTitle.png
:scale: 100 %
:alt: svgTitle example
Example of a simple svgTitle and svgLogo
Note
----
This function is completely superseeded by the more complete examples of dashboard titles available using the :py:class:`title.title` or :py:class:`app.app`
"""
svg = '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="100%" height="46" xml:space="preserve">'
LastData = datetime.today().strftime('%d %b %Y')
svg += '<text x="0" y="36" font-size="40" fill="%s" font-weight="400">%s</text>' % (settings.label_textcolor,title)
svg += '<line x1="%d" y1="0" x2="%d" y2="38" style="stroke:%s;stroke-width:1.0" />' % (xline, xline, settings.line_color)
svg += '<text x="%d" y="20" font-size="18" fill="%s" font-weight="400">%s</text>' % (xline+15, settings.label_textcolor, subtitle1)
svg += '<text x="%d" y="38" font-size="18" fill="%s" font-weight="400">%s</text>' % (xline+15, settings.label_textcolor, subtitle2)
svg += '<text x="60%%" y="24" text-anchor="middle" font-size="26" fill="%s" font-weight="400">%s</text>' % (settings.label_textcolor, LastData)
svg += '<text x="60%%" y="40" text-anchor="middle" font-size="12" fill="%s" font-weight="400">Last data update</text>' % (settings.label_textcolor)
svg += '<line x1="0" y1="45" x2="100%%" y2="45" style="stroke:%s;stroke-width:0.3" />' % (settings.line_color)
svg += '</svg>'
return svg
###########################################################################################################################################################################
# Logo
###########################################################################################################################################################################
[docs]def svgLogo():
"""
Creation of a simple SVG to display the logo of the European Commission
Returns
-------
a string containing a SVG drawing that spans for 68 pixels in width and 44 pixels in height
Example
-------
See example provided for the :py:func:`svgUtils.svgTitle` function
Note
----
This function is completely superseeded by the more complete examples of dashboard titles available using the :py:class:`title.title` or :py:class:`app.app`
"""
svg = '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="68px" height="44px" xml:space="preserve">'
svg += '<image href="https://jeodpp.jrc.ec.europa.eu/services/shared/Notebooks/images/European_Commission.svg" x="0" y="1" width="68" height="44" />'
svg += '</svg>'
return svg