pax_global_header 0000666 0000000 0000000 00000000064 13650376351 0014523 g ustar 00root root 0000000 0000000 52 comment=ee1c86d98424685736eedb6c9924a493c3a96319
nim-d3-0.1.3/ 0000775 0000000 0000000 00000000000 13650376351 0012613 5 ustar 00root root 0000000 0000000 nim-d3-0.1.3/.gitignore 0000664 0000000 0000000 00000000021 13650376351 0014574 0 ustar 00root root 0000000 0000000 nimcache/
docs/
nim-d3-0.1.3/CHANGELOG.md 0000664 0000000 0000000 00000001662 13650376351 0014431 0 ustar 00root root 0000000 0000000 ## [currently in development](https://github.com/hiteshjasani/nim-d3/compare/v0.1.3...master)
## [v0.1.3 - 2020-04-23](https://github.com/hiteshjasani/nim-d3/compare/v0.1.2...v0.1.3)
* Add selection.join
* Add ability to change axis scale
* Add support for generic scale forward and backward transforms
## [v0.1.2 - 2019-07-26](https://github.com/hiteshjasani/nim-d3/compare/v0.1.1...v0.1.2)
* Add band scale type and functions
* Add example column chart
## [v0.1.1 - 2019-07-25](https://github.com/hiteshjasani/nim-d3/compare/v0.1.0...v0.1.1)
* Removed external dependency on jsbind package
* Added routines interfacing to d3.scaleOrdinal
* Added d3-scale-chromatic schemes that work with d3.scaleOrdinal
* Improved ex3 to work with ordinal scales
* Added `docs` task to generate documentation
* Refactored filenames to more align with D3 modules to make
maintenance easier
## v0.1.0 - 2019-07-24
* Initial release with example graph
nim-d3-0.1.3/LICENSE 0000664 0000000 0000000 00000002056 13650376351 0013623 0 ustar 00root root 0000000 0000000 MIT License
Copyright (c) 2019 Hitesh Jasani
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
nim-d3-0.1.3/README.md 0000664 0000000 0000000 00000001223 13650376351 0014070 0 ustar 00root root 0000000 0000000 # nim-d3
Nim Foreign Function Interface (FFI) bindings to [d3.js](https://d3js.org).
This is in development and there is still a lot to be written. There
is the ability to create a simple graph today. See
[examples/](examples/) for usage.


## Install
nim-d3 is available in [nimble](https://github.com/nim-lang/nimble).
`nimble install d3`
or if you have a `.nimble` file then add the following.
`requires "d3 >= 0.1.2"`
NOTE: Run `nimble search d3 --ver` to find out the most recent version
of the library.
## Documentation
Clone the repo and generate the docs
`nimble docs`
nim-d3-0.1.3/d3.nimble 0000664 0000000 0000000 00000000607 13650376351 0014314 0 ustar 00root root 0000000 0000000 # Package
version = "0.1.3"
author = "Hitesh Jasani"
description = "Nim bindings to D3js"
license = "MIT"
srcDir = "src"
backend = "js"
# Dependencies
requires "nim >= 0.20.0"
# Tasks
task docs, "Build documentation":
mkDir("docs/d3")
exec "nim doc --project --index:on --outdir:docs src/d3.nim"
exec "nim buildIndex -o:docs/theindex.html docs"
nim-d3-0.1.3/examples/ 0000775 0000000 0000000 00000000000 13650376351 0014431 5 ustar 00root root 0000000 0000000 nim-d3-0.1.3/examples/.gitignore 0000664 0000000 0000000 00000000025 13650376351 0016416 0 ustar 00root root 0000000 0000000 examples
public/js/
nim-d3-0.1.3/examples/README.md 0000664 0000000 0000000 00000000526 13650376351 0015713 0 ustar 00root root 0000000 0000000 ## Quickstart
See the code for [example 5](src/pages/ex5.nim) for an example of
drawing a full chart.

## Building and Running
1. `nimble build` - build server
2. `nimble pages` - build js client pages
3. `./examples` - run server
4. Load [http://localhost:9000/](http://localhost:9000/) in browser.
nim-d3-0.1.3/examples/examples.nimble 0000664 0000000 0000000 00000001254 13650376351 0017441 0 ustar 00root root 0000000 0000000 # Package
version = "0.1.0"
author = "Hitesh Jasani"
description = "Examples using nim and D3"
license = "MIT"
srcDir = "src"
bin = @["examples"]
# Dependencies
requires "nim >= 0.20.0"
requires "karax"
from os import splitFile, addFileExt
task pages, "Build javascript pages":
if dirExists("public/js"):
rmDir("public/js")
mkDir("public/js")
for file in listfiles("src/pages/"):
let fileparts = splitFile(file)
if fileparts.ext == ".nim":
let
opts = "--hints:off"
jsfile = "public/js/" & fileparts.name.addFileExt("js")
cmd = "nim js " & opts & " -o:" & jsfile & " " & file
exec cmd
nim-d3-0.1.3/examples/nim.cfg 0000664 0000000 0000000 00000000016 13650376351 0015672 0 ustar 00root root 0000000 0000000 path="../src"
nim-d3-0.1.3/examples/src/ 0000775 0000000 0000000 00000000000 13650376351 0015220 5 ustar 00root root 0000000 0000000 nim-d3-0.1.3/examples/src/examples.nim 0000664 0000000 0000000 00000004513 13650376351 0017546 0 ustar 00root root 0000000 0000000 import asynchttpserver, asyncdispatch, strformat, strutils
type
Example = ref object
id: int
name: string
proc link(ex: Example): string =
&"""Example {ex.id}: {ex.name}"""
proc index(): string =
let examples = @[Example(id: 1, name: "Selecting elements and applying styles")
,Example(id: 2, name: "Appending elements")
,Example(id: 3, name: "Scaling using domain and range")
,Example(id: 4, name: "Simple plot of circles")
,Example(id: 5, name: "Sample scatterplot")
,Example(id: 6, name: "Seattle rainfall column chart")]
result = """
"""
for x in examples:
result.add("- " & link(x) & "
")
result.add("""
""")
proc loadJS(jspath: string): string =
let style = """
"""
&"""
{style}
"""
proc routeHandler(req: Request) {.async.} =
case req.url.path
of "/":
await req.respond(Http200, index())
else:
if startsWith(req.url.path, "/ex/"):
let
ex = req.url.path[4..^1]
path = "/js/" & ex & ".js"
await req.respond(Http200, loadJS(path))
elif startsWith(req.url.path, "/js/"):
let jsfilename = "public" & req.url.path
try:
await req.respond(Http200, readFile(jsfilename),
newHttpHeaders([("Content-Type","application/javascript")]))
except:
await req.respond(Http500, "Could not load " & jsfilename)
else:
await req.respond(Http404, "Resource not found!")
when isMainModule:
var server = newAsyncHttpServer()
echo "Point your browser to http://localhost:9000/"
waitFor server.serve(Port(9000), routeHandler)
nim-d3-0.1.3/examples/src/pages/ 0000775 0000000 0000000 00000000000 13650376351 0016317 5 ustar 00root root 0000000 0000000 nim-d3-0.1.3/examples/src/pages/ex1.nim 0000664 0000000 0000000 00000001122 13650376351 0017515 0 ustar 00root root 0000000 0000000 include karax/prelude
import d3
proc createDom(): VNode =
result = buildHtml(tdiv):
h1: text "Example 1"
p: text "Select elements and apply style"
tdiv:
text "<< "
a(href = "/"): text "home"
tdiv(id = "testarea"):
p: text "para element"
p(class = "colorable"): text "Colorable para element"
button(`type` = "button"):
text "Change color"
proc onclick(ev: Event, n: VNode) =
discard d3.select("#testarea p").style("color", "red")
discard d3.select("#testarea .colorable").style("color", "green")
setRenderer createDom
nim-d3-0.1.3/examples/src/pages/ex2.nim 0000664 0000000 0000000 00000001425 13650376351 0017524 0 ustar 00root root 0000000 0000000 include karax/prelude
import d3
proc createDom(): VNode =
result = buildHtml(tdiv):
h1: text "Example 2"
p: text "Select elements and append SVG"
tdiv:
text "<< "
a(href = "/"): text "home"
tdiv(id = "testarea"):
p: text "para element"
p(class = "colorable"): text "Colorable para element"
button(`type` = "button"):
text "Add SVG"
proc onclick(ev: Event, n: VNode) =
discard d3.select("#testarea")
.append("svg")
.attr("width", "500")
.attr("height", "300")
.append("rect")
.attr("width", "500")
.attr("height", "300")
.attr("fill", "yellow")
.attr("border", "solid 1px blue")
setRenderer createDom
setForeignNodeId("testarea")
nim-d3-0.1.3/examples/src/pages/ex3.nim 0000664 0000000 0000000 00000011330 13650376351 0017521 0 ustar 00root root 0000000 0000000 include karax/prelude
import d3
const
orig1 = 5
let
scaleLin: D3ContinuousScale = d3.scaleLinear()
.continuousDomain(0, 100)
.continuousRange(200, 500)
scaleLinR: D3ContinuousScale = d3.scaleLinear()
.continuousDomain(0, 100)
.continuousRange(500, 200)
scaleLinColor: D3ContinuousScale = d3.scaleLinear()
.continuousDomain(0, 100)
.continuousRange("brown", "steelblue")
scalePieces: D3ContinuousScale = d3.scaleLinear()
.continuousDomain(@[-1, 0, 1])
.continuousRange(@["red", "white", "blue"])
scaleOrd: D3OrdinalScale = d3.scaleOrdinal()
.ordinalDomain(@[0,1,2,3])
.ordinalRange(schemeRdBu(4))
proc runOnce() =
echo "runOnce"
proc createDom(): VNode =
result = buildHtml(tdiv):
h1: text "Example 3"
p: text "Scales - Linear ..."
tdiv:
text "<< "
a(href = "/"): text "home"
tdiv(id = "testarea"):
table:
thead:
tr:
th: text "Type"
th: text "Domain"
th: text "Range"
th: text "Original Value"
th: text "Scaled Value"
th: text "Expected Value"
tbody:
tr:
td: text "linear continuous"
td: text $continuousDomain[int](scaleLin)
td: text $continuousRange[int](scaleLin)
td: text $orig1
td(id = "scaleLin"): text "-"
td: text "215"
tr:
td: text "linear continuous (invert)"
td: text $continuousDomain[int](scaleLin)
td: text $continuousRange[int](scaleLin)
td: text "215"
td(id = "scaleLinInv"): text "-"
td: text "5"
tr:
td: text "linear continuous"
td: text $continuousDomain[int](scaleLinR)
td: text $continuousRange[int](scaleLinR)
td: text $orig1
td(id = "scaleLinR"): text "-"
td: text "485"
tr:
td: text "linear continuous"
td: text $continuousDomain[int](scaleLinColor)
td: text $continuousRange[cstring](scaleLinColor)
td: text $orig1
td(id = "scaleLinColor"): text "-"
td: text "rgb(160, 46, 49)"
tr:
td: text "linear continuous"
td: text $continuousDomain[int](scalePieces)
td: text $continuousRange[cstring](scalePieces)
td: text "-0.5"
td(id = "scalePieces1"): text "-"
td: text "rgb(255, 128, 128)"
tr:
td: text "linear continuous"
td: text $continuousDomain[int](scalePieces)
td: text $continuousRange[cstring](scalePieces)
td: text "0.5"
td(id = "scalePieces2"): text "-"
td: text "rgb(128, 128, 255)"
tr:
td: text "ordinal"
td: text $ordinalDomain[int](scaleOrd)
td: text $ordinalRange[cstring](scaleOrd)
td: text "0"
td(id = "scaleOrd1"): text "-"
td: text "#ca0020"
tr:
td: text "ordinal"
td: text $ordinalDomain[int](scaleOrd)
td: text $ordinalRange[cstring](scaleOrd)
td: text "1"
td(id = "scaleOrd2"): text "-"
td: text "#f4a582"
tr:
td: text "ordinal"
td: text $ordinalDomain[int](scaleOrd)
td: text $ordinalRange[cstring](scaleOrd)
td: text "3"
td(id = "scaleOrd3"): text "-"
td: text "#0571b0"
button(`type` = "button"):
text "Scale values"
proc onclick(ev: Event, n: VNode) =
discard select("#testarea #scaleLin").text($exec(scaleLin, orig1))
discard select("#testarea #scaleLinInv").text($invert(scaleLin, 215))
discard select("#testarea #scaleLinR").text($exec(scaleLinR, orig1))
discard select("#testarea #scaleLinColor").text($exec(scaleLinColor,
orig1))
discard select("#testarea #scalePieces1").text($exec(scalePieces, -0.5))
discard select("#testarea #scalePieces2").text($exec(scalePieces, 0.5))
discard select("#testarea #scaleOrd1").text($exec(scaleOrd, 0))
discard select("#testarea #scaleOrd2").text($exec(scaleOrd, 1))
discard select("#testarea #scaleOrd3").text($exec(scaleOrd, 3))
setRenderer createDom
setForeignNodeId("testarea")
#--------------------------------------
redrawSync()
runOnce()
nim-d3-0.1.3/examples/src/pages/ex4.nim 0000664 0000000 0000000 00000002754 13650376351 0017534 0 ustar 00root root 0000000 0000000 include karax/prelude
import d3
type
DataPt = ref object
x: float
y: float
let rawdata: seq[DataPt] = @[DataPt(x: 10.0, y: 12.1)
,DataPt(x: 22.0, y: 32.5)
,DataPt(x: 101.3, y: 5.0)
,DataPt(x: 196.2, y: 8.5)
,DataPt(x: 2.7, y: 174.3)
,DataPt(x: 100.0, y: 100.0)
,DataPt(x: 125.4, y: 184.9)]
proc runOnce() =
let
width = 200
height = 200
let svg = select("#testarea")
.append("svg")
.attr("width", width)
.attr("height", height)
discard svg.append("rect")
.attr("width", width)
.attr("height", height)
.attr("fill", "yellow")
.attr("fill-opacity", 0.3)
discard svg.selectAll("circle")
.data(rawdata)
.enter().append("circle")
.attr("cx", proc(d: DataPt): float = d.x)
.attr("cy", proc(d: DataPt): float = d.y)
.attr("r", 12.5)
proc createDom(): VNode =
result = buildHtml(tdiv):
h1: text "Example 4: Simple plot circles"
tdiv:
text "<< "
a(href = "/"): text "home"
tdiv(id = "testarea")
ul:
li: text "Circles are only drawn once on page load and will not be redrawn even if karax redraws the vdom"
li: text "No scaling has been applied yet so circles are plotted with their y values upside down"
setRenderer createDom
setForeignNodeId("testarea")
#--------------------------------------
redrawSync()
runOnce()
nim-d3-0.1.3/examples/src/pages/ex5.nim 0000664 0000000 0000000 00000010117 13650376351 0017525 0 ustar 00root root 0000000 0000000 from sequtils import map
import strformat
include karax/prelude
import d3
type
DataPt = ref object
x: float
y: float
let rawdata: seq[DataPt] = @[DataPt(x: 10.0, y: 112.1)
,DataPt(x: 22.0, y: 132.5)
,DataPt(x: 101.3, y: 15.0)
,DataPt(x: 196.2, y: 18.5)
,DataPt(x: 2.7, y: 1174.3)
,DataPt(x: 100.0, y: 1100.0)
,DataPt(x: 125.4, y: 1184.9)
,DataPt(x: 145.0, y: 575.0)]
proc runOnce() =
let
svgWidth = 400
svgHeight = 300
insetLeft = 80
insetBottom = 50
gWidth = svgWidth - insetLeft
gHeight = svgHeight - insetBottom
minX = min(map(rawdata, proc(d: DataPt): float = d.x))
maxX = max(map(rawdata, proc(d: DataPt): float = d.x))
minY = min(map(rawdata, proc(d: DataPt): float = d.y))
maxY = max(map(rawdata, proc(d: DataPt): float = d.y))
xScale: D3ContinuousScale = d3.scaleLinear()
.continuousDomain(minX, maxX)
.continuousRange(0, gWidth)
yScale: D3ContinuousScale = d3.scaleLinear()
.continuousDomain(minY, maxY)
# Use with origin in top-left corner
.continuousRange(gHeight, 0)
xAxis: D3Axis = axisBottom(xScale)
yAxis: D3Axis = axisLeft(yScale)
let svg = select("#testarea")
.append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight)
discard svg.append("rect")
.attr("width", svgWidth)
.attr("height", svgHeight)
.attr("fill", "yellow")
.attr("fill-opacity", 0.3)
let g = svg.append("g")
.attr("width", gWidth)
.attr("height", gHeight)
# Origin is top-left corner, use with yRange [gHeight 0]
.attr("transform", translate(insetLeft, 0))
discard g.append("g")
.attr("class", "x-axis axis")
.attr("transform", translate(0, gHeight))
.call(xAxis)
discard g.append("g")
.attr("class", "y-axis axis")
.call(yAxis)
discard svg.append("text")
.attr("text-anchor", "middle")
.attr("transform", translate(insetLeft.float + gWidth/2,
svgHeight.float - insetBottom/4))
.text("x axis label")
discard svg.append("text")
.attr("text-anchor", "middle")
.attr("transform", translateAndRotate(insetLeft/3, gHeight/2, -90))
.text("y axis label")
let tooltip = select("body")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0)
discard g.selectAll("circle")
.data(rawdata)
.enter().append("circle")
.attr("cx", proc(d: DataPt): float = exec(xScale, d.x))
.attr("cy", proc(d: DataPt): float = exec(yScale, d.y))
.attr("r", 12.5)
.attr("fill-opacity", 0.2)
.attr("stroke", "blue")
.attr("fill", "blue")
.on("mouseover", proc(d: DataPt) =
discard tooltip
.html("x: " & $d.x & "
" & "y: " & $d.y)
.style("left", $(d3.event().pageX + 25) & "px")
.style("top", $d3.event().pageY & "px")
discard tooltip.transition()
.duration(200)
.style("opacity", 0.9))
.on("mouseout", proc(d: DataPt) =
discard tooltip.transition()
.duration(500)
.style("opacity", 0.0))
proc createDom(): VNode =
result = buildHtml(tdiv):
h1: text "Example 5: Sample scatterplot"
tdiv:
text "<< "
a(href = "/"): text "home"
tdiv(id = "testarea")
ul:
li: text "Circles are only drawn once on page load and will not be redrawn even if karax redraws the vdom"
li: text "Input data is scaled"
li: text "Circles have styling applied"
li: text "Graph axes and labels"
li: text "Hover tooltip on data points"
setRenderer createDom
setForeignNodeId("testarea")
#--------------------------------------
redrawSync()
runOnce()
nim-d3-0.1.3/examples/src/pages/ex6.nim 0000664 0000000 0000000 00000011335 13650376351 0017531 0 ustar 00root root 0000000 0000000 from sequtils import map
import strformat
include karax/prelude
import d3
type
DataPt = ref object
day: int
rain: float
# data is rainfall in Seattle from Feb 1, 2019 - Feb 14, 2019
# https://www.ncdc.noaa.gov/cdo-web/datasets/GHCND/stations/GHCND:USW00024233/detail
let rawdata: seq[DataPt] = @[DataPt(day: 1, rain: 0.68)
,DataPt(day: 2, rain: 0.03)
,DataPt(day: 3, rain: 0.09)
,DataPt(day: 4, rain: 0.13)
,DataPt(day: 5, rain: 0.0)
,DataPt(day: 6, rain: 0.0)
,DataPt(day: 7, rain: 0.0)
,DataPt(day: 8, rain: 0.46)
,DataPt(day: 9, rain: 0.16)
,DataPt(day: 10, rain: 0.16)
,DataPt(day: 11, rain: 1.22)
,DataPt(day: 12, rain: 0.86)
,DataPt(day: 13, rain: 0.02)
,DataPt(day: 14, rain: 0.12)]
proc runOnce() =
let
svgWidth = 600
svgHeight = 300
insetLeft = 80
insetBottom = 50
gWidth = svgWidth - insetLeft
gHeight = svgHeight - insetBottom
minY = min(map(rawdata, proc(d: DataPt): float = d.rain))
maxY = max(map(rawdata, proc(d: DataPt): float = d.rain))
domX = map(rawdata, proc(d: DataPt): cstring = cstring($d.day))
xScale: D3BandScale = scaleBand()
.bandDomain(domX)
.bandRange(0, gWidth)
.paddingInner(0.1)
.paddingOuter(0.2)
.align(0.5)
.round(true)
yScale: D3ContinuousScale = scaleLinear()
.continuousDomain(minY, maxY)
# Use with origin in top-left corner
.continuousRange(gHeight, 0)
xAxis: D3Axis = axisBottom(xScale)
yAxis: D3Axis = axisLeft(yScale)
let svg = select("#testarea")
.append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight)
discard svg.append("rect")
.attr("width", svgWidth)
.attr("height", svgHeight)
.attr("fill", "yellow")
.attr("fill-opacity", 0.3)
let g = svg.append("g")
.attr("width", gWidth)
.attr("height", gHeight)
# Origin is top-left corner, use with yRange [gHeight 0]
.attr("transform", translate(insetLeft, 0))
discard g.append("g")
.attr("class", "x-axis axis")
.attr("transform", translate(0, gHeight))
.call(xAxis)
discard g.append("g")
.attr("class", "y-axis axis")
.call(yAxis)
discard svg.append("text")
.attr("text-anchor", "middle")
.attr("transform", translate(insetLeft.float + gWidth/2,
svgHeight.float - insetBottom/4))
.text("Feb 2019 (day)")
discard svg.append("text")
.attr("text-anchor", "middle")
.attr("transform", translateAndRotate(insetLeft/3, gHeight/2, -90))
.text("Rainfall (inches)")
let tooltip = select("body")
.append("div")
.attr("class", "tooltip")
.style("opacity", 0)
discard g.selectAll("rect")
.data(rawdata)
.enter().append("rect")
.attr("fill", "steelblue")
.attr("x", proc(d: DataPt): int = exec(xScale, d.day))
.attr("width", xScale.bandwidth())
.attr("y", proc(d: DataPt): float = exec(yScale, d.rain))
.attr("height", proc(d: DataPt): float = gHeight.float - exec(yScale, d.rain))
.on("mouseover", proc(d: DataPt) =
discard tooltip
.html("day: " & $d.day & "
" & "rain: " &
$d.rain & " inches")
.style("left", $(d3.event().pageX + 25) & "px")
.style("top", $d3.event().pageY & "px")
discard tooltip.transition()
.duration(200)
.style("opacity", 0.9))
.on("mouseout", proc(d: DataPt) =
discard tooltip.transition()
.duration(500)
.style("opacity", 0.0))
proc createDom(): VNode =
result = buildHtml(tdiv):
h1: text "Example 6: Seattle Rainfall Column Chart"
tdiv:
text "<< "
a(href = "/"): text "home"
tdiv(id = "testarea")
ul:
li: text "Rainfall in Seattle from Feb 1, 2019 to Feb 14, 2019"
li:
span: text "Data sourced from "
a(href = "https://www.ncdc.noaa.gov/cdo-web/datasets/GHCND/stations/GHCND:USW00024233/detail"): text "noaa.gov"
setRenderer createDom
setForeignNodeId("testarea")
#--------------------------------------
redrawSync()
runOnce()
nim-d3-0.1.3/img/ 0000775 0000000 0000000 00000000000 13650376351 0013367 5 ustar 00root root 0000000 0000000 nim-d3-0.1.3/img/ex5.png 0000664 0000000 0000000 00000120550 13650376351 0014601 0 ustar 00root root 0000000 0000000 PNG
IHDR ; Gn! HiCCPICC Profile HWXS[RIhH RK E*I ĐD.*v*Z Y+Z*oR`]{}|9){ :5<4 OR aMJMc L g_.eE(C Qޯ(9_EO @ y /Ke }zfT@l B,U,5.Q5T$s
ɲ nzV!?h߂U"K !CGB<*/oC;O839yaE%P\˛K^b(4,2^Y3ۭQJLW>=(ULRۣ|90!vB 68\gdùByXF6#>vg8loO?Ibko!7ŢuP6LyNB)qbldxe6 %!j~lZ,<^c/˓Ջ-1\U JTA,xICaڱv$IS/)-iq07BT^T1҂DuxF6o|:D,# @g@f#E5#PH~!Y!(ZdfU9 y
d8Z2x5D\sPSdžhF1$Cp#nx4CeOxB <"\'tnO/}SL 0B振k '@~ȍ3qx M[
_u]cGq`÷Nڞ,ʞ~!u}|U%;Nb#X`aDZfvTWc*'ܵz@X|?,8KTb7ŕGb_S¼. ePg
' 0~ m+dj ~10?a`< L],3rU`
v=`?hGI+upn` ABG1b"Έ;"aH4"H"A;YT![:g0r9t H
b(
5@P;tꃲ(4fh1Z.C+Zt7ڈD/N9ڏLcbqX,
d<jWNq]
ēp>×UN?_}`Jp&I,LB)pp>M݄D"I'zç1MM\J@KsZ>iN4m
MA[FA;AM{M4z}~^=Z-О]ݨ}EEV3MXBe^].G;OZM~=^^R]z%[Ow1053clcat
{
&V5dbL;&\ϼ8l{pĒ
#xg4(HhTfGcqqJ&&D&MΘ4??lwLQS'x٦[M/EI֛25gg1?fc[8n;ːf*YY}
-mVVIVZݷZXgZn`3Ǧ-Vd;;{s9j9}s78;NN"jΨys((QQn\...G3GG^4i16cƬsvWO\mwƻ-rkq{wvArX؍coy2<'xKm]}'g9_o|#
8qquXt7vYj[?e;ٻ/B\Cd!Bq8s9'BЈвж0VY}#ND""WFq:nxsǟE%DUE=vEL@'z½ILS,ƮgDĸĻω?H+mbHĻIId)uRBSVtN3i&4RZraN9tʍSf2-w:ySwjy܌>>\,X#W fd|:G$9*Mrbsv#Kr$g(!uJ;ɢd| n/)+V<@^,YKf=-/i6>?u圅se21u"\H]EV-z]w-%f%JTTVz6--X~ɗ2AمrOKK/c參2-ZqqdōA+w[UkՍkXkּY;}Q)uVFW6Yb*Q55Kjml1xc&M>no%bKc]mVO%o;OuMoCsguuuLw-G=na^}`b?|c>98Tֈ4jk5u66wſ/qHQãˏQ<^|Dɬ][t鉧D9kβ?py/\huCyvͫvqǮ]9y5.^q#ƭSnvzv;;w.GWv_~rNΣC^zncO%OO*Z<{HOxO~.}>[5/^3K}_^Z7cߴ?xv]{;?|81ӁH*?;~n`ࠔ'㩶hf&