def getYearLabels(userOptions={}): global items cfg = {} options = {"y": 0.667} options.update(userOptions) yearCol = "year" if yearCol not in items[0]: # print("`dateColumn` needs to be set in config yml to support timelineTracks layout") return (None, None) years = [item[yearCol] for item in items] minYear = min(years) maxYear = max(years) + 1 nUnit = 1.0 / (maxYear - minYear) labels = [] for i in range(maxYear - minYear): x = 0.5 y = options["y"] year = minYear + i z = mu.norm(year, (minYear, maxYear)) + nUnit * 0.5 # center labels += [ round(x, PRECISION), round(y, PRECISION), round(z, PRECISION), year ] return (cfg, labels)
def getYearSounds(spriteData, userOptions={}): global items cfg = { "dimension": 2 # use z-axis } yearCol = "year" if yearCol not in items[0]: print("Could not find column %s in items, please add this column to metadata cols with 'type' = 'int'" % yearCol) sys.exit() sprites = spriteCount = None if spriteData is not None: sprites = spriteData["sprites"] spriteCount = len(sprites) years = [item[yearCol] for item in items] minYear = min(years) maxYear = max(years) + 1 nUnit = 1.0 / (maxYear-minYear) sounds = [] for i in range(maxYear-minYear): x = y = 0.5 year = minYear + i z = mu.norm(year, (minYear, maxYear)) + nUnit*0.5 # center sounds += [round(x, PRECISION), round(y, PRECISION), round(z, PRECISION)] if sprites is not None: spriteIndex = mu.roundInt(1.0 * z * (spriteCount-1)) sprite = sprites[spriteIndex] sounds += [sprite["start"], sprite["dur"]] return (cfg, sounds)
def getCountryLabels(userOptions={}): global items cfg = {} options = {"y": 0.5} options.update(userOptions) countryCol = "country" latCol = "lat" lonCol = "lon" if countryCol not in items[0]: print( "`countryColumn` needs to be set in config yml to support country labels; they will not show otherwise." ) return (None, None) if latCol not in items[0] or lonCol not in items[0]: # print("`latitudeColumn` and `latitudeColumn` need to be set in config yml to support country labels; they will not show otherwise.") return (None, None) latRange = (90.0, -90.0) lonRange = (-180.0, 180.0) groups = lu.groupList(items, countryCol) # group by country counts = [group["count"] for group in groups] minCount, maxCount = (min(counts), max(counts)) labels = [] for group in groups: firstItem = group["items"][0] label = firstItem[countryCol] lon = firstItem[lonCol] lat = firstItem[latCol] y = options["y"] x = 1.0 - mu.norm(lon, lonRange) z = 1.0 - mu.norm(lat, latRange) # HACK: offset z slightly to acommodate size of bar w = mu.norm(group["count"], (minCount, maxCount)) w = mu.lerp((0.01, 1.0), w) # assume height is half the depth; divide by 6 for radius calculation (see geometry.js) radius = 0.5 / 6.0 * w + 0.005 z = z - radius labels += [ round(x, PRECISION), round(y, PRECISION), round(z, PRECISION), label ] return (cfg, labels)
def getGeographyBarsLayout(userOptions={}): global items cfg = { "layout": "bars" } latCol = "lat" lonCol = "lon" if latCol not in items[0] or lonCol not in items[0]: print("`latitudeColumn` and `latitudeColumn` need to be set in config yml to support geographyBars layout") return (False, False) # create unique key for lat lon for i, item in enumerate(items): items[i]["lonLatKey"] = (mu.roundInt(item[lonCol]*PRECISION), mu.roundInt(item[latCol]*PRECISION)) latRange = (90.0, -90.0) lonRange = (-180.0, 180.0) dimensions = 3 groups = lu.groupList(items, "lonLatKey") # group by lat lon counts = [group["count"] for group in groups] minCount, maxCount = (min(counts), max(counts)) # assign position values values = np.zeros(len(items) * dimensions) for group in groups: y = mu.norm(group["count"], (minCount, maxCount)) y = mu.lerp((0.01, 1.0), y) for item in group["items"]: itemIndex = item["index"] x = 1.0 - mu.norm(item[lonCol], lonRange) z = 1.0 - mu.norm(item[latCol], latRange) itemY = y # a bit of a hack to ensure highighted items are visible if itemHasStory(item): itemY = y + 1.05 values[itemIndex*dimensions] = round(x, PRECISION) values[itemIndex*dimensions+1] = round(itemY, PRECISION) values[itemIndex*dimensions+2] = round(z, PRECISION) values = values.tolist() return (cfg, values)
def getCategoryTimelineHotspot(view, content): global items global sets year = None if "year" in content: year = content["year"] elif "visibleTimeRange" in content: year = mu.roundInt(mu.lerp(tuple(content["visibleTimeRange"]), 0.5)) if year is None: print("Need to set year or time range in content") return None yearCol = "year" if yearCol not in items[0]: print( "Could not find column %s in items, please add this column to metadata cols with 'type' = 'int'" % yearCol) return None years = [item[yearCol] for item in items] minYear = min(years) maxYear = max(years) + 1 nUnit = 1.0 / (maxYear - minYear) if "category" not in sets: print("Could not find column 'category' in sets") return None categories = sets["category"] if "category" not in content: print("Could not find column 'category' in content") return None if content["category"] not in categories: print("Could not find %s in categories" % content["category"]) return None categoryCount = len(categories) categoryIndex = categories.index(content["category"]) # place at in the center of the year z = mu.norm(year, (minYear, maxYear)) + nUnit * 0.5 # place at center of region x = 1.0 - 1.0 * categoryIndex / (categoryCount - 1) # place at top of bounds y = 0.0 return { "x": round(x, PRECISION), "y": round(y, PRECISION), "z": round(z, PRECISION) }
def getGeographyBarsLayout(userOptions={}): global items cfg = {"layout": "bars"} latCol = "lat" lonCol = "lon" if latCol not in items[0] or lonCol not in items[0]: print( "Could not find column (%s, %s) in items, please add these columns to metadata cols with 'type' = 'float'" % (lonCol, latCol)) sys.exit() # create unique key for lat lon for i, item in enumerate(items): items[i]["lonLatKey"] = (mu.roundInt(item[lonCol] * PRECISION), mu.roundInt(item[latCol] * PRECISION)) latRange = (90.0, -90.0) lonRange = (-180.0, 180.0) dimensions = 3 groups = lu.groupList(items, "lonLatKey") # group by lat lon counts = [group["count"] for group in groups] minCount, maxCount = (min(counts), max(counts)) # assign position values values = np.zeros(len(items) * dimensions) for group in groups: y = mu.norm(group["count"], (minCount, maxCount)) y = mu.lerp((0.01, 1.0), y) for item in group["items"]: itemIndex = item["index"] x = 1.0 - mu.norm(item[lonCol], lonRange) z = 1.0 - mu.norm(item[latCol], latRange) values[itemIndex * dimensions] = round(x, PRECISION) values[itemIndex * dimensions + 1] = round(y, PRECISION) values[itemIndex * dimensions + 2] = round(z, PRECISION) values = values.tolist() return (cfg, values)
def getTimelineTunnelLayout(userOptions={}): global items options = { "thickness": 0.2 } options.update(userOptions) cfg = {} yearCol = "year" dimensions = 3 if yearCol not in items[0]: print("`dateColumn` needs to be set in config yml to support timelineTunnel layout") return (False, False) years = [item[yearCol] for item in items] minYear = min(years) maxYear = max(years) + 1 nUnit = 1.0 / (maxYear-minYear) groups = lu.groupList(items, yearCol) # group by year groups = sorted(groups, key=lambda group: group[yearCol]) nThickness = options["thickness"] minDistance = 0.5-nThickness maxDistance = 0.5 count = 0 values = np.zeros(len(items) * dimensions) for i, group in enumerate(groups): minZ = mu.norm(group[yearCol], (minYear, maxYear)) maxZ = minZ + nUnit for j, item in enumerate(group["items"]): index = item["index"] x = y = 0.5 z = mu.randomUniform(minZ, maxZ, seed=count+5) # angle = mu.randomUniform(0, 360, seed=count+7) angle = mu.randomUniform(-240, 60, seed=count+7) distance = mu.randomUniform(minDistance, maxDistance, seed=count+9) # ensure story items are visible if itemHasStory(item): distance = minDistance * 0.8 x, y = mu.translatePoint(x, y, distance, angle) values[index*dimensions] = round(x, PRECISION) values[index*dimensions+1] = round(y, PRECISION) values[index*dimensions+2] = round(z, PRECISION) count += 1 values = values.tolist() return (cfg, values)
def getTimelineTunnelLayout(userOptions={}): global items options = {"thickness": 0.2} options.update(userOptions) cfg = {} yearCol = "year" dimensions = 3 if yearCol not in items[0]: print( "Could not find column %s in items, please add this column to metadata cols with 'type' = 'int'" % yearCol) sys.exit() years = [item[yearCol] for item in items] minYear = min(years) maxYear = max(years) + 1 nUnit = 1.0 / (maxYear - minYear) groups = lu.groupList(items, yearCol) # group by year groups = sorted(groups, key=lambda group: group[yearCol]) nThickness = options["thickness"] count = 0 values = np.zeros(len(items) * dimensions) for i, group in enumerate(groups): minZ = mu.norm(group[yearCol], (minYear, maxYear)) maxZ = minZ + nUnit for j, item in enumerate(group["items"]): index = item["index"] x = y = 0.5 z = mu.randomUniform(minZ, maxZ, seed=count + 5) # angle = mu.randomUniform(0, 360, seed=count+7) angle = mu.randomUniform(-240, 60, seed=count + 7) distance = mu.randomUniform(0.5 - nThickness, 0.5, seed=count + 9) x, y = mu.translatePoint(x, y, distance, angle) values[index * dimensions] = round(x, PRECISION) values[index * dimensions + 1] = round(y, PRECISION) values[index * dimensions + 2] = round(z, PRECISION) count += 1 values = values.tolist() return (cfg, values)
def getCategoryYearLabels(userOptions={}): global items global categories cfg = {} options = {"y": 0.667, "labelEveryYearUnit": 5} options.update(userOptions) yearCol = "year" categoryCol = "category" if yearCol not in items[0]: # print("`dateColumn` needs to be set in config yml to support timelineTracks layout") return (None, None) if categoryCol not in items[0]: # print("`groupByColumn` needs to be set in config yml to support timelineTracks layout") return (None, None) categoryCount = len(categories) years = [item[yearCol] for item in items] minYear = min(years) maxYear = max(years) + 1 nUnit = 1.0 / (maxYear - minYear) labels = [] for i in range(maxYear - minYear): year = minYear + i if year % options["labelEveryYearUnit"] > 0: continue y = options["y"] z = mu.norm(year, (minYear, maxYear)) # beginning of year for j, category in enumerate(categories): x = 1.0 - 1.0 * j / (categoryCount - 1) labels += [ round(x, PRECISION), round(y, PRECISION), round(z, PRECISION), category["text"] ] return (cfg, labels)
def getSphereCategoryTimelineLayout(userOptions={}): global items global sets cfg = {"layout": "spheres"} categoryCol = "category" yearCol = "year" if yearCol not in items[0]: print( "Could not find column %s in items, please add this column to metadata cols with 'type' = 'int'" % yearCol) sys.exit() if categoryCol not in sets: print( "Could not find column %s in sets, please add this column to metadata cols with 'asIndex' = true" % categoryCol) sys.exit() categorySet = sets[categoryCol] categoryCount = len(categorySet) dimensions = 3 groups = lu.groupList(items, yearCol) # group by year groups = sorted(groups, key=lambda group: group[yearCol]) years = [item[yearCol] for item in items] minYear = min(years) maxYear = max(years) + 1 nUnit = 1.0 / (maxYear - minYear) # determine category sphere count range minCount = 9999999999 maxCount = 0 for i, group in enumerate(groups): subgroups = lu.groupList(group["items"], categoryCol) # group by category for subgroup in subgroups: minCount = min(minCount, subgroup["count"]) maxCount = max(maxCount, subgroup["count"]) groups[i]["categoryGroups"] = subgroups # assign position values values = np.zeros(len(items) * dimensions) for i, group in enumerate(groups): z = mu.norm( group[yearCol], (minYear, maxYear)) + nUnit * 0.5 # place spheres in the center of the year subgroups = group["categoryGroups"] subgroupLookup = lu.createLookup(subgroups, categoryCol) for j, category in enumerate(categorySet): x = 1.0 - 1.0 * j / (categoryCount - 1) categoryKey = str(j) if categoryKey in subgroupLookup: subgroup = subgroupLookup[categoryKey] y = mu.norm(subgroup["count"], (minCount, maxCount)) y = mu.lerp((0.01, 1.0), y) for catItem in subgroup["items"]: itemIndex = catItem["index"] values[itemIndex * dimensions] = round(x, PRECISION) values[itemIndex * dimensions + 1] = round(y, PRECISION) values[itemIndex * dimensions + 2] = round(z, PRECISION) values = values.tolist() return (cfg, values)
def getSphereCategoryTimelineLayout(userOptions={}): global items global categories cfg = { "layout": "spheres" } categoryCol = "category" yearCol = "year" if yearCol not in items[0]: print("`dateColumn` needs to be set in config yml to support timelineTracks layout") return (False, False) if categoryCol not in items[0]: print("`groupByColumn` needs to be set in config yml to support timelineTracks layout") return (False, False) categoryCount = len(categories) dimensions = 3 groups = lu.groupList(items, yearCol) # group by year groups = sorted(groups, key=lambda group: group[yearCol]) years = [item[yearCol] for item in items] minYear = min(years) maxYear = max(years) + 1 nUnit = 1.0 / (maxYear-minYear) # determine category sphere count range minCount = 9999999999 maxCount = 0 for i, group in enumerate(groups): subgroups = lu.groupList(group["items"], categoryCol) # group by category for subgroup in subgroups: minCount = min(minCount, subgroup["count"]) maxCount = max(maxCount, subgroup["count"]) groups[i]["categoryGroups"] = subgroups # assign position values values = np.zeros(len(items) * dimensions) for i, group in enumerate(groups): z = mu.norm(group[yearCol], (minYear, maxYear)) + nUnit*0.5 # place spheres in the center of the year subgroups = group["categoryGroups"] subgroupLookup = lu.createLookup(subgroups, categoryCol) for j, category in enumerate(categories): x = 1.0 - 1.0 * j / (categoryCount-1) categoryKey = category["text"] if categoryKey in subgroupLookup: subgroup = subgroupLookup[categoryKey] y = mu.norm(subgroup["count"], (minCount, maxCount)) y = mu.lerp((0.01, 1.0), y) for catItem in subgroup["items"]: itemIndex = catItem["index"] cy = y # a bit of a hack to ensure highighted items are visible if itemHasStory(catItem): cy = y + 1.25 values[itemIndex*dimensions] = round(x, PRECISION) values[itemIndex*dimensions+1] = round(cy, PRECISION) values[itemIndex*dimensions+2] = round(z, PRECISION) values = values.tolist() return (cfg, values)