Compare commits

...

17 Commits

Author SHA1 Message Date
Aadhavan Srinivasan 485a3efd8f Added HTML sanitization for translate server
Aadhavan Srinivasan 17878106d4 Added maximum length to input text
Aadhavan Srinivasan 67fc0d4297 Added changes from production use
Aadhavan Srinivasan 6716a4cd40 Updated styling for buttons and textbox; made some flexbox changes
Aadhavan Srinivasan 4e4570ed03 Font resizing stuff; looks better on desktop and mobile now
Aadhavan Srinivasan 12d53370fb Updated translations DB
Aadhavan Srinivasan 11b9635e50 Added responsive font resizing (with viewport width); removed dangling 'p' tag
Aadhavan Srinivasan d769797136 Added horizontal alignment for each language text; moved Hindi text down
Aadhavan Srinivasan 4804497b3c Updated positioning of text
Aadhavan Srinivasan 80529e5882 Updated translations DB from production use
Aadhavan Srinivasan f6abc396b6 Added intermediate responsive font sizes
Aadhavan Srinivasan 094d756a07 Updated flexbox styling
Aadhavan Srinivasan fca0d3a5fa Added help text
Aadhavan Srinivasan 5efb1ad322 Updated translations DB
Aadhavan Srinivasan 2e85a7c2b3 Added comments; remove SVG 'g' elements with no children
Aadhavan Srinivasan 4658ec5a9d Draw translation text above map, so that it isn't obscured
Aadhavan Srinivasan 364a64f320 Merge pull request 'Styling changes to Resize SVG' () from resizeSvg into master
Reviewed-on: 

@ -9,14 +9,17 @@ require (
cloud.google.com/go/compute/metadata v0.6.0 // indirect
cloud.google.com/go/longrunning v0.6.2 // indirect
cloud.google.com/go/translate v1.12.3 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/google/s2a-go v0.1.8 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
github.com/googleapis/gax-go/v2 v2.14.0 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/jmoiron/sqlx v1.4.0 // indirect
github.com/mattn/go-sqlite3 v1.14.24 // indirect
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
go.opentelemetry.io/otel v1.29.0 // indirect

@ -11,6 +11,8 @@ cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3
cloud.google.com/go/translate v1.12.3 h1:XJ7LipYJi80BCgVk2lx1fwc7DIYM6oV2qx1G4IAGQ5w=
cloud.google.com/go/translate v1.12.3/go.mod h1:qINOVpgmgBnY4YTFHdfVO4nLrSBlpvlIyosqpGEgyEg=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@ -25,12 +27,16 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gT
github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
github.com/googleapis/gax-go/v2 v2.14.0 h1:f+jMrjBPl+DL9nI4IQzLUxMq7XrAqFYB7hBPqMNIe8o=
github.com/googleapis/gax-go/v2 v2.14.0/go.mod h1:lhBCnjdLrWRaPvLWhmc8IS24m9mr07qSYnHncrgo+zk=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=

@ -16,6 +16,8 @@ import (
translate "cloud.google.com/go/translate/apiv3"
"cloud.google.com/go/translate/apiv3/translatepb"
"github.com/microcosm-cc/bluemonday"
)
const project_id string = "india-translate-testing-452100"
@ -72,6 +74,7 @@ var lang_codes []string = []string{
}
var db *sqlx.DB
var p *bluemonday.Policy
func cleanup() {
db.Close()
@ -149,6 +152,7 @@ func translateTextHelper(projectID string, sourceLang string, targetLang string,
func handler(w http.ResponseWriter, r *http.Request) {
queries := r.URL.Query()
toTranslate := queries["query"][0]
toTranslate = p.Sanitize(toTranslate)
if ok, translation := getCachedTranslation(toTranslate); ok {
translationJson, _ := json.Marshal(translation)
@ -182,6 +186,10 @@ func main() {
panic(err)
}
defer db.Close()
// HTML sanitization
p = bluemonday.UGCPolicy()
// Catch signal
sigs := make(chan os.Signal, 1)
signal.Notify(sigs)

@ -57,7 +57,9 @@
.translationText, .romanizationText {
visibility: hidden;
font-family: "Noto Sans";
font-size:1.2em;
/* font-size:1.2em; */
/* font-size: max(0.8vmax, 16px);*/
font-size: 1.2em;
}
.romanizationText {
@ -67,13 +69,11 @@
.languageText {
pointer-events: none;
font-weight: bold;
font-size: 1em;
/* font-size: 1em;*/
font-size: max(0.7vmax, 14px);
font-family: sans-serif;
visibility: hidden;
}
.language:hover ~ .languageText {
visibility: visible;
}
.district {
stroke: white;
@ -113,11 +113,84 @@
transition: fill-opacity 0.3s;
}
section#textStuff {
min-width: 30em;
/* flex: 0 1 0; */
flex: 0 0 auto;
}
section#textStuff ul {
font-size: 1.2em;
line-height: 1.5rem;
max-width: 35em;
}
section#textStuff li {
margin: 1em 0;
}
#query {
font-size: 1em;
padding: 6px 8px;
margin-right: 1em;
}
#submitButton {
appearance: none;
background-color: #2ea44f;
border: 1px solid rgba(27, 31, 35, .15);
border-radius: 6px;
box-shadow: rgba(27, 31, 35, .1) 0 1px 0;
box-sizing: border-box;
color: #fff;
cursor: pointer;
display: inline-block;
font-size: 1em;
font-weight: 600;
line-height: 1.2em;
padding: 6px 16px;
position: relative;
text-align: center;
text-decoration: none;
user-select: none;
-webkit-user-select: none;
touch-action: manipulation;
vertical-align: middle;
white-space: nowrap;
}
#submitButton:focus:not(:focus-visible):not(.focus-visible) {
box-shadow: none;
outline: none;
}
#submitButton:hover {
background-color: #2c974b;
}
#submitButton:focus {
box-shadow: rgba(46, 164, 79, .4) 0 0 0 3px;
outline: none;
}
#submitButton:disabled {
background-color: #94d3a2;
border-color: rgba(27, 31, 35, .1);
color: rgba(255, 255, 255, .8);
cursor: default;
}
#submitButton:active {
background-color: #298e46;
box-shadow: rgba(20, 70, 32, .2) 0 1px 0 inset;
}
/* Position map load spinner in the middle of the SVG */
#svgContainer {
position: relative;
display: inline-block;
flex-grow: 1;
flex-shrink: 1;
flex-grow: 1;
max-width: 100%;
}
@ -178,7 +251,7 @@
#fetchingText {
visibility: hidden;
}
@media only screen and (max-width: 768px) {
@media only screen and (max-width: 1366px) {
main {
flex-direction: column;
align-items: stretch;
@ -187,14 +260,22 @@
stroke-width: 0.1;
}
.translationText, .romanizationText {
font-size: 0.7em;
font-size: 0.8em;
}
.languageText {
font-size: 0.6em;
}
}
}
/* @media only screen and (max-width: 768px) {
.translationText, .romanizationText {
font-size: 0.8em;
}
.languageText {
font-size: 0.6em;
}
} */
</style>
</head>
<body>
@ -203,14 +284,22 @@
<h1>Indian Translate</h1>
<form hx-get="/submit" hx-swap=none hx-indicator="#loading-screen" hx-on::before-request="hideTranslationsAndShowText(event.detail.xhr.response)" hx-on::after-request="updateTranslations(event.detail.xhr.response)" class="translateForm" method="get">
<label for="query">Enter text to translate:</label>
<input type="text" name="query" id="query" required/>
<input type="submit" value="Translate"/>
<input type="text" maxlength="20" name="query" id="query" required/>
<input type="submit" id="submitButton" value="Translate"/>
</form>
<progress id="loading-screen" class="loading-indicator"></progress>
<h3 id="fetchingText">Fetching translations...</h3>
<noscript>
<h2>This website requires javascript to work.</h2>
</noscript>
<ul>
<li>Enter text in English. Translations are displayed on the map, along with romanizations (translation converted to Latin script).</li>
<li>Hover over a language region (denoted by color) to see the language spoken in the region. Language boundaries were drawn using census data; they may be inaccurate.</li>
<li>Translations are retrieved from Google Translate - they may be innacurate.</li>
<li>Romanizations are made with the <kbd>ai4bharat-transliteration</kbd> AI model, developed by <a href="https://ai4bharat.iitm.ac.in/">Ai4Bharat</a>.</li>
</ul>
</section>
<section id="svgContainer">
@ -220,6 +309,7 @@
<script type="text/javascript" src="index.js"></script>
<script type="text/javascript" src="updateTranslations.js"></script>
<script type="text/javascript" src="svgHoverListener.js"></script>
</body>

@ -1,28 +1,28 @@
const defaultColor = "#555555"
const languages = {
tamil: {name: "Tamil", color: "#75d795", code: "ta", districts: []},
malayalam: {name: "Malayalam", color: "#ff7c7c", code: "ml", districts: []},
kannada: {name: "Kannada", color: "#ffe77c", code: "kn", districts: []},
telugu: {name: "Telugu", color: "#7c9dff", code: "te", districts: []},
marathi: {name: "Marathi", color: "#e0ff7c", code: "mr", districts: []},
konkani: {name: "Konkani", color: "#9b7cff", code: "gom", districts: []},
hindi: {name: "Hindi", color: "#d17cff", code: "hi", districts: []},
gujarati: {name: "Gujarati", color: "#7cffee", code: "gu", districts: []},
oriya: {name: "Oriya", color: "#9bcc9f", code: "or", districts: []},
bengali: {name: "Bengali", color: "#bf9a77", code: "bn", districts: []},
punjabi: {name: "Punjabi", color: "#e84a35", code: "pa", districts: []},
mizo: {name: "Mizo", color: "#a6a4de", code: "lus", districts: []},
assamese: {name: "Assamese", color: "#c9535b", code: "as", districts: []},
bhojpuri: {name: "Bhojpuri", color: "#b3b876", code: "bho", districts: []},
manipuri: {name: "Manipuri", color: "#c9afad", code: "mni-Mtei", districts: []},
dogri: {name: "Dogri", color: "#9595e6", code: "doi", districts: []},
nepali: {name: "Nepali", color: "#71998e", code: "ne", districts: []},
urdu: {name: "Urdu", color: "#3fa179", code: "ur", districts: []},
tulu: {name: "Tulu", color: "#dedc52", code: "tcy", districts: []},
maithali: {name: "Maithali", color: "#4472a6", code: "mai", districts: []},
sindhi: {name: "Sindhi", color: "#e89931", code: "sd", districts: []},
awadhi: {name: "Awadhi", color: "#847fb5", code: "awa", districts: []},
tamil: {name: "Tamil", color: "#75d795", code: "ta", text_align: "start", districts: []},
malayalam: {name: "Malayalam", color: "#ff7c7c", code: "ml", text_align: "end", districts: []},
kannada: {name: "Kannada", color: "#ffe77c", code: "kn", text_align: "middle", districts: []},
telugu: {name: "Telugu", color: "#7c9dff", code: "te", text_align: "start", districts: []},
marathi: {name: "Marathi", color: "#e0ff7c", code: "mr", text_align: "middle", districts: []},
konkani: {name: "Konkani", color: "#9b7cff", code: "gom", text_align: "end", districts: []},
hindi: {name: "Hindi", color: "#d17cff", code: "hi", text_align: "middle", districts: []},
gujarati: {name: "Gujarati", color: "#7cffee", code: "gu", text_align: "middle", districts: []},
oriya: {name: "Oriya", color: "#9bcc9f", code: "or", text_align: "middle", districts: []},
bengali: {name: "Bengali", color: "#bf9a77", code: "bn", text_align: "middle", districts: []},
punjabi: {name: "Punjabi", color: "#e84a35", code: "pa", text_align: "middle", districts: []},
mizo: {name: "Mizo", color: "#a6a4de", code: "lus", text_align: "middle", districts: []},
assamese: {name: "Assamese", color: "#c9535b", code: "as", text_align: "middle", districts: []},
bhojpuri: {name: "Bhojpuri", color: "#b3b876", code: "bho", text_align: "end", districts: []},
manipuri: {name: "Manipuri", color: "#c9afad", code: "mni-Mtei", text_align: "middle", districts: []},
dogri: {name: "Dogri", color: "#9595e6", code: "doi", text_align: "middle", districts: []},
nepali: {name: "Nepali", color: "#71998e", code: "ne", text_align: "middle", districts: []},
urdu: {name: "Urdu", color: "#3fa179", code: "ur", text_align: "middle", districts: []},
tulu: {name: "Tulu", color: "#dedc52", code: "tcy", text_align: "end", districts: []},
maithali: {name: "Maithali", color: "#4472a6", code: "mai", text_align: "middle", districts: []},
sindhi: {name: "Sindhi", color: "#e89931", code: "sd", text_align: "middle", districts: []},
awadhi: {name: "Awadhi", color: "#847fb5", code: "awa", text_align: "middle", districts: []},
};
// Credit: https://www.artcraftblend.com/blogs/colors/shades-of-pastel
@ -213,9 +213,14 @@ function stateOrDistrictOrLanguage(d) {
// const mapWidth = document.getElementById("indiaMap").getAttribute("width")
// const mapHeight = document.getElementById("indiaMap").getAttribute("height")
const mapWidth = /*window.innerWidth - */document.querySelector("#svgContainer").offsetWidth * 0.85;
let mapWidth = /*window.innerWidth - */document.querySelector("#svgContainer").offsetWidth * 0.85;
// const mapHeight = document.querySelector("#svgContainer").offsetHeight;
const mapHeight = (window.innerHeight - document.querySelector("#svgContainer").getBoundingClientRect().top);
let mapHeight = (window.innerHeight - document.querySelector("#svgContainer").getBoundingClientRect().top);
if (window.innerWidth <= 768) {
mapWidth *= 1.5;
mapHeight *= 1.5;
}
const svg = d3.select("#svgContainer")
.append('svg')
@ -227,7 +232,12 @@ const svg = d3.select("#svgContainer")
// .call(responsivefy);
function drawMap(world) {
const projection = d3.geoMercator().fitSize([mapWidth, mapHeight], world)
let projection = null;
if (window.innerWidth > 1366) {
projection = d3.geoMercator().fitSize([mapWidth, mapHeight], world)
} else {
projection = d3.geoMercator().fitWidth(mapWidth, world)
}
const path = d3.geoPath().projection(projection);
requestAnimationFrame(() => {
@ -235,9 +245,9 @@ function drawMap(world) {
const bbox = newSvg.node().getBBox();
const originalWidth = +newSvg.attr("width");
newSvg
// .attr("viewBox", `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`)
.attr("viewBox", `0 ${bbox.y} ${originalWidth} ${bbox.height}`)
// .attr("width", mapHeight.toString())
.attr("viewBox", `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`)
// .attr("viewBox", `0 ${bbox.y} ${originalWidth} ${bbox.height}`)
.attr("width", bbox.width)
.attr("height", bbox.height)
// .style("display", "block")
// .style("max-height", "100%"); // optional: keep it scalable in a flexbox
@ -252,6 +262,11 @@ function drawMap(world) {
states.append("path")
.attr("d", path)
.attr("class", d => stateOrDistrictOrLanguage(d))
.attr("id", function(d) {
if (stateOrDistrictOrLanguage(d) === "language") {
return languages[d.properties.lang_name.toLowerCase()].code + "langMap";
}
})
.attr("fill", function(d) {
if (stateOrDistrictOrLanguage(d) === "language") {
return languages[d.properties.lang_name.toLowerCase()].color;
@ -272,7 +287,8 @@ function drawMap(world) {
.append("title") // Tooltip
.text(d => d.properties.district);
states.append("text")
const textItems = svg.selectAll("g.textItem").data(world.features).enter().append("g");
textItems.append("text")
.attr("x", function(d) {
if (stateOrDistrictOrLanguage(d) == "language") {
rtv = projection(d3.geoCentroid(d))[0];
@ -280,7 +296,10 @@ function drawMap(world) {
rtv -= 20;
}
if (d.properties.lang_name == "Tamil") {
rtv += 20;
rtv -= 10;
}
if (d.properties.lang_name == "Kannada" && window.innerWidth <= 768) {
rtv += 15;
}
if (d.properties.lang_name == "Maithali") {
rtv += 10;
@ -298,10 +317,15 @@ function drawMap(world) {
if (stateOrDistrictOrLanguage(d) == "language") {
rtv = projection(d3.geoCentroid(d))[1]
if (d.properties.lang_name == "Kannada") {
rtv += 15;
if (window.innerWidth > 768) {
rtv += 10;
}
}
if (d.properties.lang_name == "Tamil") {
rtv -= 20;
rtv -= 10;
}
if (d.properties.lang_name == "Malayalam") {
rtv += 10;
}
if (d.properties.lang_name == "Gujarati") {
rtv -= 10;
@ -315,12 +339,15 @@ function drawMap(world) {
if (d.properties.lang_name == "Bengali") {
rtv += 25;
}
if (d.properties.lang_name == "Hindi") {
if (window.innerWidth > 768) {
rtv += 3 * parseFloat(getComputedStyle(document.documentElement).fontSize); // rem
}
}
return rtv
}
})
.attr("text-anchor", "middle")
.attr("fill", "black")
.attr("class", "translationText")
.attr("id", function(d) {
if (stateOrDistrictOrLanguage(d) == "language") {
@ -335,18 +362,18 @@ function drawMap(world) {
} else {
d3.select(this).remove() // Only add this attribute if the element is a language
}
});
})
.attr("text-anchor", d => typeof d.properties.lang_name != "undefined" && languages[d.properties.lang_name.toLowerCase()].text_align)
.attr("fill", "black")
// Romanization
states.append("text")
textItems.append("text")
.attr("x", d => stateOrDistrictOrLanguage(d) == "language" ?
document.getElementById(d.properties.lang_code + "Text").getAttribute("x") :
projection(d3.geoCentroid(d))[0])
.attr("y", d => stateOrDistrictOrLanguage(d) == "language" ?
parseFloat(document.getElementById(d.properties.lang_code + "Text").getAttribute("y")) + parseFloat(getComputedStyle(document.getElementsByClassName('translationText')[0]).getPropertyValue('font-size')) :
projection(d3.geoCentroid(d))[1])
.attr("text-anchor", "middle")
.attr("fill", "black")
.attr("class", "romanizationText")
.attr("id", function(d) {
if (stateOrDistrictOrLanguage(d) == "language") {
@ -359,18 +386,18 @@ function drawMap(world) {
if (!stateOrDistrictOrLanguage(d) == "language") {
d3.select(this).remove() // Only add this attribute if the element is a language
}
});
})
.attr("text-anchor", d => typeof d.properties.lang_name != "undefined" && languages[d.properties.lang_name.toLowerCase()].text_align)
.attr("fill", "black")
// Language
states.append("text")
textItems.append("text")
.attr("x", d => stateOrDistrictOrLanguage(d) == "language" ?
document.getElementById(d.properties.lang_code + "Text").getAttribute("x") :
projection(d3.geoCentroid(d))[0])
.attr("y", d => stateOrDistrictOrLanguage(d) == "language" ?
parseFloat(document.getElementById(d.properties.lang_code + "Text").getAttribute("y")) - parseFloat(getComputedStyle(document.getElementsByClassName('translationText')[0]).getPropertyValue('font-size')) :
projection(d3.geoCentroid(d))[1])
.attr("text-anchor", "middle")
.attr("fill", "black")
.attr("class", "languageText")
.attr("id", function(d) {
if (stateOrDistrictOrLanguage(d) == "language") {
@ -391,11 +418,30 @@ function drawMap(world) {
d3.select(this).remove() // Only add this attribute if the element is a language
}
})
.attr("text-anchor", d => typeof d.properties.lang_name != "undefined" && languages[d.properties.lang_name.toLowerCase()].text_align)
.attr("fill", "black")
let allLangs = []
const coordinates = [77.69916967457782,23.389970772934166];
const [xCoord, yCoord] = projection(coordinates);
// Add a listener to toggle the language display when a language is hovered on the map
document.querySelectorAll(".language").
forEach(function(element) {
element.addEventListener("mouseover", toggleLanguageDisplay);
element.addEventListener("mouseout", toggleLanguageDisplay);
});
// Remove all 'g' elements that have no children (they don't have a language, translation, or romanization). This must mean they accidentally got created when I created the 'textItems' variable
document.querySelectorAll("g").
forEach(function(element) {
if (element.childElementCount == 0) {
element.remove()
}
});
// svg.append("text")
// .attr("x", xCoord)
// .attr("y", yCoord)

@ -0,0 +1,8 @@
function toggleLanguageDisplay(event) {
if (event.type == "mouseover") {
document.getElementById(this.id.replace(/langMap$/, '') + "Language").style.visibility = "visible";
}
if (event.type == "mouseout") {
document.getElementById(this.id.replace(/langMap$/, '') + "Language").style.visibility = "hidden";
}
}

Binary file not shown.
Loading…
Cancel
Save